{"id":49749,"date":"2020-04-16T06:00:48","date_gmt":"2020-04-16T10:00:48","guid":{"rendered":"https:\/\/www.shamusyoung.com\/twentysidedtale\/?p=49749"},"modified":"2020-04-16T10:51:56","modified_gmt":"2020-04-16T14:51:56","slug":"revisiting-a-dead-engine","status":"publish","type":"post","link":"https:\/\/www.shamusyoung.com\/twentysidedtale\/?p=49749","title":{"rendered":"Revisiting a Dead Engine"},"content":{"rendered":"<p>This is a second entry, following the <a href=\"?p=49740\">first one<\/a>, which makes this a series now.\u00a0 If you remember, my son Issac made a level for Garry&#8217;s mod that I thought was cool, and I foolishly assumed it would be easy to get it translated to a modern engine. Here&#8217;s the original level:<\/p>\n<p><div class='imagefull'><img src='https:\/\/www.shamusyoung.com\/twentysidedtale\/images\/basalt1.jpg' width=100% alt='This is what it looks like in Garry&apos;s Mod.' title='This is what it looks like in Garry&apos;s Mod.'\/><\/div><div class='mouseover-alt'>This is what it looks like in Garry&apos;s Mod.<\/div><\/p>\n<p>It was unreasonably difficult to extract all of those polygons and render them in another game engine. Let&#8217;s talk about why.<!--more--><\/p>\n<p>The <a href=\"https:\/\/en.wikipedia.org\/wiki\/Source_2\">Source Engine 2<\/a> has a bit of a murky history. Source is Valve&#8217;s in-house engine. It&#8217;s the technology driving <em>Half-Life 2<\/em> and its attendant episodes. It&#8217;s what they used for the <em>Left 4 Dead<\/em> games and <em>DOTA 2<\/em>. It was the engine behind Valve&#8217;s beloved <em>Portal<\/em> games. Wikipedia will tell you that Source 2 was officially announced in 2015, but in truth there wasn&#8217;t a clear division between Source engine (Original Flavor) and Source Engine 2 (Electric Boogaloo). Rather it was a gradual process of evolution, and Valve arbitrarily decided that 2015 was the cutoff point between old and new. <em>Left 4 Dead 2<\/em> levels (2009) could do a ton of things that <em>Half-Life 2<\/em> levels (2004) couldn&#8217;t. In <em>Portal 2<\/em>, they finally got around to adding realtime shadows<span class='snote' title='1'>The bit where Wheatly guides you through the darkness with his eye-light was a first for the engine. This is apparently significantly different from the shadow-casting flashlights of <em>Left 4 Dead<\/em> in ways I&#8217;ve never bothered to look up.<\/span> with dynamic light sources, about a decade after everyone else. The <em>Left 4 Dead<\/em> levels were incompatible with the engine used in <em>Half-Life 2<\/em>, so I fully expected that to be the recognized demarcation between New and Old, but apparently that didn&#8217;t happen until <em>DOTA 2<\/em>.<\/p>\n<p>The point here is that the original Source Engine was descended from the Quake 2 engine, and many of the underlying data structures are the same. So even though I&#8217;m trying to read in a level that was made this week, this involves reaching all the way back to 1997 or so and speaking the language of late-90s game engines. There are things that this engine does that made no damn sense in 2010, and are downright archaic by 2020.<\/p>\n<p>Source engine maps are stored in .BSP files.\u00a0<a href=\"https:\/\/developer.valvesoftware.com\/wiki\/Source_BSP_File_Format\">This site<\/a>, which I linked in the previous entry, is effectively our <a href=\"https:\/\/en.wikipedia.org\/wiki\/Rosetta_Stone\">Rosetta Stone<\/a>. Without this, I&#8217;d never have any hope of sorting this out. That documents how a .BSP map is put together.<\/p>\n<h3>A Throwback Data Structure<\/h3>\n<p>The entire file format reflects a very vanilla C design style. It&#8217;s very obvious that the level compiler just writes raw data structures to disk. A data structure might look like this:<\/p>\n<pre lang=\"c\">\r\nstruct lump_t\r\n{\r\n  int offset; \/\/ offset into file (bytes)\r\n  int length; \/\/ length of lump (bytes)\r\n  int version; \/\/ lump format version\r\n  char ident[4]; \/\/ lump ident code - 4 bytes\r\n};<\/pre>\n<p>Incidentally: This data structure being called a &#8220;lump&#8221; is somewhat reminiscent of 90s video game data naming schemes. In those days everyone ran into the problem of, &#8220;Hey, I need to jam a bunch of unrelated data into a single monolithic file. What should I call these files?&#8221; And so we wound up with WAD files (Doom) BLOB files<span class='snote' title='2'>I can&#8217;t remember which engine used blobs. I&#8217;m 51% sure it was Descent.<\/span>, and such. This lump structure was the obvious and minimum-effort way to store crap in the days when C ruled the programming world. You open a file, and then shove the contents of a data structure directly to disk.<\/p>\n<p>The file headers contain information like &#8220;The vertices are stored 34,532 bytes into the file, and the vertex data is 23,964 bytes long. Since you know vertex data is 12 bytes long<span class='snote' title='3'>A floating-point number is 4 bytes long. You need 3 of these for an X, Y, Z position.<\/span>, you can divide that length by 12 and know that there are 1,997 vertices. This sort of business is perfect if you&#8217;re a frugal late-90s programmer and you want to skip around the file, loading only the things you need right now, because you can&#8217;t afford to decadently waste a whopping 2 megabytes of main memory on a data structure you&#8217;re not currently using.<\/p>\n<p>The lump_t structure defined in the code above would be exactly 16 bytes long<span class='snote' title='4'>Each int is 4 bytes. A char is one byte, but there are four of those.<\/span>. So you take a raw memory pointer and aim it at your data structure, then you tell the program &#8220;Starting at this spot, write the next 16 bytes of memory into this file on disk.&#8221; Then when it was time to load the level, you&#8217;d do the reverse. You&#8217;d aim the pointer at the start of the structure, and take\u00a0 the next 16 bytes out of the file and shove them into that spot of memory. If you made a mistake and wound up out of alignment with the file during the reading process, then you were doomed. Even being off by a single byte would turn the data into a random mess.<\/p>\n<p>To be honest, I kind of liked the simplicity of those days. Everything either worked perfectly or failed catastrophically. While young people get nervous handling raw memory pointers, I thought it was a great approach in situations like this.<\/p>\n<p>Regardless of how you felt about those days, they are over. Modern languages don&#8217;t want you juggling raw memory pointers and randomly writing blocks of bytes into bits of memory. In C#, this sort of behavior isn&#8217;t just discouraged, it&#8217;s forbidden<span class='snote' title='5'>Disclaimer: There might be some black-magic hack around it. I wouldn&#8217;t know. I&#8217;ve only been using the language for a couple of years and there&#8217;s a lot I don&#8217;t know.<\/span>. You can&#8217;t write to memory like that, filling several variables at once. Moreover, this sort of business would break encapsulation. Even if you were allowed direct, unsupervised memory access, your file-loading code wouldn&#8217;t be allowed to write into your level-data structures. And even if you somehow found a way around all of that<span class='snote' title='6'>You could make all the data structures public, which is a huge <a href=\"?p=35692\">OOP<\/a> no-no. That&#8217;s what I did, and I don&#8217;t even feel guilty.<\/span>, you can&#8217;t be guaranteed that the memory layouts would match.\u00a0I don&#8217;t understand the rules, but people more familiar with this sort of thing tell me that the compiler might mess with the layout of your data, padding it with a byte here or there for some sort of optimization. And even if you got over all of that, I&#8217;m sure there are lots of other problems I&#8217;m overlooking<span class='snote' title='7'>I&#8217;ll bet array data is stored completely differently.<\/span>.<\/p>\n<p>None of this means you can&#8217;t read the file. It just means that reading the file is a little strange and inconvenient. You can&#8217;t fill an entire data structure in a single read operation, but you can read it into memory one variable at a time.<\/p>\n<h3>The Problem with Fans<\/h3>\n<p>I mentioned in the previous entry that <a href=\"https:\/\/developer.valvesoftware.com\/wiki\/Source_BSP_File_Format\">this document<\/a> is &#8211; as far as I can tell &#8211; the only publicly-available document on how these files work. I also mentioned that it doesn&#8217;t tell you everything you need. Don&#8217;t get me wrong, this is an incredible document and I&#8217;m very glad for the hard work that the author put into it. It&#8217;s just that sometimes things get overlooked, possibly because they&#8217;re things that &#8220;everyone knows&#8221;. Which is fine, until you reach the point where everyone forgets about it and all we have left is the document.<\/p>\n<p>In the modern world, we&#8217;d generally store geometry as vertices (a bunch of positions in 3D space) and a list of indexes describing how those vertices form triangles. It&#8217;s basically a 3D game of connect-the dots.<\/p>\n<p>This was not the case in the world of 1997, before the rise of graphics cards. At the time, you generally had to shove your vertex data at the renderer one at a time. Thus an important optimization was to send as few verts as possible.<\/p>\n<p>Consider this arrangement of polygons:<\/p>\n<p><div class='imagefull'><img src='https:\/\/www.shamusyoung.com\/twentysidedtale\/images\/basalt_verts.jpg' width=100% alt='This is the best illustration I could come up with in 5 minutes or less.' title='This is the best illustration I could come up with in 5 minutes or less.'\/><\/div><div class='mouseover-alt'>This is the best illustration I could come up with in 5 minutes or less.<\/div><\/p>\n<p>Today, we&#8217;d give the graphics card all the verts up front. We&#8217;d send them all in one bulk load, and then refer back to them.<\/p>\n<p>&#8220;Draw the triangle defined by A-B-G&#8221;.<\/p>\n<p>&#8220;Now do B-C-G.&#8221;<\/p>\n<p>&#8220;Then do C-D-G.&#8221;<\/p>\n<p>And so on, until it&#8217;s all drawn.<\/p>\n<p>But in <a href=\"https:\/\/en.wikipedia.org\/wiki\/Thorn_(letter)\"><b>\u00fe<\/b><\/a>e Olden Days, you couldn&#8217;t &#8220;store&#8221; verts on the graphics hardware like that<span class='snote' title='8'>Often because this machine didn&#8217;t have any.<\/span>. Which means that sending triangles like this would be painfully inefficient. You&#8217;ll notice in the list above, I sent B twice, C twice, and G 3 times. In fact, to send the entire mesh, I&#8217;d have to send every point twice. Except for G, which would get sent once for every triangle, a total of 6 times.<\/p>\n<p>That would be horribly wasteful. But! There&#8217;s a way we can avoid this. We can use a triangle <strong>fan<\/strong>. To describe a fan, I&#8217;ll just paste in one of my comments:<\/p>\n<pre lang=\"c\">\r\n\/\/Now each face has a list if triangle fans, seperated by -1 indexes. We need\r\n\/\/to pull these apart and make them into regular triangles.\r\n\/\/For you kids, a fan is a series of triangles built around a common hub.\r\n\/\/The first vertex in the series is the first vertex for ALL triangles.\r\n\/\/After the first two verticies, each additional vertex adds a new triangle.\r\n\/\/Each triangle is defined as so: (index 0),(previous index), (current index).\r\n\/\/So the series A B C D E would result in 3 triangles:\r\n\/\/ABC , ACD, ADE. The fan system is pointless in a modern hardware context,\r\n\/\/so we just need discrete triangles. The following loop does this.\r\nfor (int f = 0; f > _face_count; f++) {\r\n  \/\/complex hoodoo goes here\r\n}\r\n<\/pre>\n<p>So the triangle fan for the image above would go G A B C D E F A.\u00a0 Every vertex gets sent once, except for A which needs to be sent a second time to close the loop.<\/p>\n<p>Not a bad system for 1997, but it&#8217;s totally useless today. These days we send all the verts in one bulk load and can refer back to them as many times as we like for no additional cost<span class='snote' title='9'>I mean, aside from the cost of drawing the resulting triangles.<\/span>. The graphics card can go to work on the entire job without needing to wait around to be spoon-fed vertices one at a time.<\/p>\n<p><div class='imagefull'><img src='https:\/\/www.shamusyoung.com\/twentysidedtale\/images\/basalt_wireframe.jpg' width=100% alt='I know this looks like a big tangle of random lines, but once you do the work to create the view frustum, transform the verts, render the polygons, and calculate the lights, it can really look like something sort of half-assed.' title='I know this looks like a big tangle of random lines, but once you do the work to create the view frustum, transform the verts, render the polygons, and calculate the lights, it can really look like something sort of half-assed.'\/><\/div><div class='mouseover-alt'>I know this looks like a big tangle of random lines, but once you do the work to create the view frustum, transform the verts, render the polygons, and calculate the lights, it can really look like something sort of half-assed.<\/div><\/p>\n<p>What I&#8217;m getting at here, is that the vert data inside of a BSP stores all triangles as triangle fans. The document doesn&#8217;t tell you, and there&#8217;s no way to intuit that from the code. You just have to know it. I had to find the original Quake 2 source and dig around in the code for ages until I found this accidental secret.<\/p>\n<p>Also, the system for putting meshes together is hilariously indirect. It doesn&#8217;t just list the verts you need to create the fans. No, it lists everything as edges &#8211; as pairs of verts. Then there&#8217;s another layer of indirection where it lists edges and tells you if you need to reverse the pair. Then it lists those segments in long chains, with extra data describing the original surface these polygons belong to so you know which way they&#8217;re facing. This structure is great if you&#8217;re trying to pull the data into memory and use it to directly render crap, but it&#8217;s a lot of hassle if you need to translate the data into something a modern engine will accept.<\/p>\n<p>Anyway, I got it sorted out.<\/p>\n<p><div class='imagefull'><img src='https:\/\/www.shamusyoung.com\/twentysidedtale\/images\/basalt_path_tracing.jpg' width=100% alt='This shot is taken from roughly the some position as the header image of this post and the previous wireframe image, if you want to compare the original level to the Unity version. The glowing floor is actually supposed to be water, which I have not yet (and probably won&apos;t) implement.' title='This shot is taken from roughly the some position as the header image of this post and the previous wireframe image, if you want to compare the original level to the Unity version. The glowing floor is actually supposed to be water, which I have not yet (and probably won&apos;t) implement.'\/><\/div><div class='mouseover-alt'>This shot is taken from roughly the some position as the header image of this post and the previous wireframe image, if you want to compare the original level to the Unity version. The glowing floor is actually supposed to be water, which I have not yet (and probably won&apos;t) implement.<\/div><\/p>\n<p>Also, I finally got path tracing \/ ray tracing to work, but that&#8217;s another post.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This is a second entry, following the first one, which makes this a series now.\u00a0 If you remember, my son Issac made a level for Garry&#8217;s mod that I thought was cool, and I foolishly assumed it would be easy to get it translated to a modern engine. Here&#8217;s the original level: It was unreasonably [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[66],"tags":[],"class_list":["post-49749","post","type-post","status-publish","format-standard","hentry","category-programming"],"_links":{"self":[{"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=\/wp\/v2\/posts\/49749","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=49749"}],"version-history":[{"count":23,"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=\/wp\/v2\/posts\/49749\/revisions"}],"predecessor-version":[{"id":49772,"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=\/wp\/v2\/posts\/49749\/revisions\/49772"}],"wp:attachment":[{"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=49749"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=49749"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=49749"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}