{"id":51107,"date":"2020-11-10T06:00:47","date_gmt":"2020-11-10T11:00:47","guid":{"rendered":"https:\/\/www.shamusyoung.com\/twentysidedtale\/?p=51107"},"modified":"2020-11-10T07:05:40","modified_gmt":"2020-11-10T12:05:40","slug":"project-bug-hunt-7-it-works-kinda","status":"publish","type":"post","link":"https:\/\/www.shamusyoung.com\/twentysidedtale\/?p=51107","title":{"rendered":"Project Bug Hunt #7: It Works! (Kinda.)"},"content":{"rendered":"<p>In the previous entry, I talked about all the models I made in Blender. However, in that entry I kinda skipped over a step. Before I could import those models into my&#8230; game&#8230; demo&#8230; experiment&#8230; <b>thing<\/b>, I had to make my own importer, because the built-in Unity importer is a hot mess.<\/p>\n<p>It&#8217;s frustrating, because the existing system is 95% of the way there. But if you want that last 5%, then you need a lot of extra code. Fixing this within the Unity source code would be pretty easy, but for indie beggars that can&#8217;t pay for source access, the importer is locked inside of a black box. Take it or leave it.<\/p>\n<p>Hey, it&#8217;s been ages since the last Terrible Car Analogy, so let&#8217;s do one of those:<\/p>\n<blockquote><p>You have a muscle car. You want to make it way more powerful by giving all those extra parts that gearheads like to add to increase the engine&#8217;s horsepower. However, you&#8217;re not able to add things under the hood. Anything you add needs to be stuck outside the car somewhere. Your improvements are going to be ugly as hell, and not nearly as effective as they would be if you could edit the stuff under the hood.<\/p><\/blockquote>\n<p>So what&#8217;s wrong with Unity&#8217;s code to import Blender models?<\/p>\n<p><!--more--><\/p>\n<h3>A Lot<\/h3>\n<p>Let me count the ways&#8230;<\/p>\n<p><b>1. It can&#8217;t handle multiple textures on a single model.<\/b><\/p>\n<p><div class='imagefull'><img src='https:\/\/www.shamusyoung.com\/twentysidedtale\/images\/bughunt_import3.jpg' width=100% alt='I guess this is supposed to be a space-photocopier? I don&apos;t know. I love 3D modeling, but I&apos;m not particularly good at 3D design.' title='I guess this is supposed to be a space-photocopier? I don&apos;t know. I love 3D modeling, but I&apos;m not particularly good at 3D design.'\/><\/div><div class='mouseover-alt'>I guess this is supposed to be a space-photocopier? I don&apos;t know. I love 3D modeling, but I&apos;m not particularly good at 3D design.<\/div><\/p>\n<p>Let&#8217;s say you&#8217;ve made a model of a car. The body uses the texture RedPaint1.png. The bumpers use DirtyChrome.png. The tires use DeepTread5.png. The interior uses FineCorinthianLeather.png. And so on.<\/p>\n<p>The Unity importer doesn&#8217;t like this, so it will throw away all but one of the textures and apply the remaining texture to the entire vehicle. So you end up with (say) an entire car mode of solid chrome, or whatever. If you complain about this on the forums, people will tell you that this half-assed system is &#8220;helping&#8221; you because you&#8217;re not supposed to put too many textures on a single object because that keeps Unity from rendering objects in batches, which in turn might slow down your game. That&#8217;s true in some cases, but these things are highly situational. If this is the big fancy central set-piece object of the scene, then you don&#8217;t need to worry about batching because you&#8217;ll only have one of these things in the scene at the time.<\/p>\n<p>To fix this, you need to jump over to Unity and break the object into many sub-objects. That fixes the problem, but maybe you don&#8217;t WANT your Unity object to be fragmented like this because that will make it harder to work on. The proper solution would be to have Unity break the object apart on the way in. That might still have downsides, but this would be far more sensible than applying a single texture to the whole object, because the latter renders the object useless.<\/p>\n<p><b>2. It doesn&#8217;t properly translate coordinate systems.<\/b><\/p>\n<p>In Unity, the X axis is left\/right, the Y axis is up\/down, and the Z axis is forward\/back. In Blender, the X is still left\/right, but the Y axis is forward\/back and Z is up\/down.<\/p>\n<p>These are both valid coord systems and each one has its own benefits. I prefer the Blender system because That&#8217;s how I structured my engines back in the day when I used to make my own. I like this because you can throw away Z (up\/down) and use the X+Y for an overhead map.<\/p>\n<p>In Unity, the priority was to make the same engine useful for 2D and 3D projects at the same time. In Unity, if you throw away Z (forward\/backward) then you&#8217;re essentially working with a side-scrolling style game like a platformer. The Y coord is still up-down, so the physics engine can still do its thing without needing to care if you&#8217;re making a 2D or 3D game.<\/p>\n<p>The point is that these coordinate systems are very different, which means that it takes a lot of advanced big-brain math to convert between these different systems. Check it out:<\/p>\n<ol>\n<li>Take Blender&#8217;s Z value and assign it to Unity&#8217;s Y.<\/li>\n<li>Take Blender&#8217;s Y value, multiply by -1, and assign it to Unity&#8217;s Z.<\/li>\n<li>That&#8217;s it. You&#8217;re done.<\/li>\n<\/ol>\n<p>So I lied. It&#8217;s trivial. Literally one line of code:<\/p>\n<pre lang=\"cs\">unity_vert = Vector3 (blender.x, blender.z, -blender.y);<\/pre>\n<p>But for whatever reason, Unity&#8217;s importer takes the Blender model through a complex conversion process and then stops one step short of the finish line by skipping the most trivial step. If you&#8217;re importing (say) a refrigerator, it will appear in Unity on its back, with the door facing up.<\/p>\n<p>But wait! It&#8217;s worse!<\/p>\n<p>Unity then covers up this crime by\u00a0 taking this newly-created model and manually rotating it forward 90 degrees on the X axis. This means the fridge will appear to be standing up in the world as you expect. However, if you start rotating the fridge &#8211; or if you ever tell it to return to default rotation &#8211; then it&#8217;s going to end up in an unexpected position and you&#8217;ll probably assume you&#8217;ve got a bug in your code, because why would you suspect a model importer of treachery?<\/p>\n<p><b>3. It messes up the surface normals.<\/b><\/p>\n<p><div class='imagefull'><img src='https:\/\/www.shamusyoung.com\/twentysidedtale\/images\/bughunt_import1.jpg' width=100% alt='On the left and right, you see  the tubes are lit from the right, dark on the left. The tubes in the center have bad normals and thus their lighting doesn&apos;t match what&apos;s going on in the scene.' title='On the left and right, you see  the tubes are lit from the right, dark on the left. The tubes in the center have bad normals and thus their lighting doesn&apos;t match what&apos;s going on in the scene.'\/><\/div><div class='mouseover-alt'>On the left and right, you see  the tubes are lit from the right, dark on the left. The tubes in the center have bad normals and thus their lighting doesn&apos;t match what&apos;s going on in the scene.<\/div><\/p>\n<p>The surface normals tell the rendering system which way a polygon is facing. If you mess it up, then you&#8217;ll have a wall that faces (say) west, but it reacts to light as if it was (say) facing forward. Or whatever. The result is confusing nonsensical lighting.<\/p>\n<p>I&#8217;m not 100% sure what causes this, but as far as I can tell, Unity <b>hates<\/b> when you have a model that isn&#8217;t an enclosed solid. Like, let&#8217;s say you make a cylinder. It has the polygons for the round outer wall, more polygons for the top, and finally some to make the bottom. Like a soda can, this is an enclosed solid. But then you delete the top and bottom because you won&#8217;t need them. This object is a vertical pipe that runs floor-to-ceiling, so the player will never see the ends.<\/p>\n<p>But without the ends, the object is now a hollow tube, like an empty toilet paper roll. For whatever reason, Unity won&#8217;t \/ can&#8217;t use the perfectly good normals you had in Blender, and you wind up with nonsense garbage normals.<\/p>\n<p>So don&#8217;t cut the ends off of the pipe, right? That works, unless you run into problem #1 above and you MUST tear the object into pieces to keep Unity from forgetting your texture info. All of those pieces will be non-solid, and thus it&#8217;s random what Unity will do to the normals. I&#8217;ve had two pipes in the same model. One was an exact clone of the other. They were lit properly in Blender, but after Unity got done importing it, one pipe was correct and the other was inside-out, and nothing I did on the Blender side could fix this<span class='snote' title='1'>Blender lets you flip the normals, turning the object inside-out. But this doesn&#8217;t fix it. It&#8217;s like no matter which way the polygons face, Unity <b>insists<\/b> on making them backwards.<\/span>.<\/p>\n<p><b>4. The Unity importer is shamefully slow.\u00a0<\/b><\/p>\n<p><div class='imagefull'><img src='https:\/\/www.shamusyoung.com\/twentysidedtale\/images\/bughunt_import2.jpg' width=100% alt='I tried making sleeping pods like you&apos;d see in Prey \/ Deus Ex Human Revolution. So far nobody was able to recognize them as such.' title='I tried making sleeping pods like you&apos;d see in Prey \/ Deus Ex Human Revolution. So far nobody was able to recognize them as such.'\/><\/div><div class='mouseover-alt'>I tried making sleeping pods like you&apos;d see in Prey \/ Deus Ex Human Revolution. So far nobody was able to recognize them as such.<\/div><\/p>\n<p>I have no idea what the deal is, but I suspect this is a contributing factor in why Unity games are notorious for unreasonable load times. Unity makes this sad, half-converted object and stores it as a &#8220;prefab&#8221;. A prefab is like an original blueprint for an object, or collection of objects. So when you want to place those pipes in the world, you have to make a clone of this prefab.<\/p>\n<p>I wrote some code to go through all the Blender models, create an instance of each one, do the trivial math to get it properly converted to the local coordinate system, and then save it in my own homebrew binary file format. That process takes about 1.4 seconds to process my entire library.<\/p>\n<p>Then when it&#8217;s time to run the game, it loads in all of those binary files. That part takes 0.004 seconds.<\/p>\n<p>That&#8217;s bonkers. I realize the first thing is doing a lot more than the second thing, but it&#8217;s not doing <b>three orders of magnitude<\/b> more. As far as I can tell, there&#8217;s something absurd with the code to make a copy of a prefab that makes it take bloody ages.<\/p>\n<p>So here I am, bitching about Unity again. If I hate it so much, why don&#8217;t I embrace one of the many <a href=\"https:\/\/en.wikipedia.org\/wiki\/Godot_(game_engine)\">alternatives<\/a>?<\/p>\n<h3>Code Reuse<\/h3>\n<p>I know this topic is a bit of a re-run for some of you. I&#8217;m compelled to complain about this at least once in every programming series. This is mostly for new-ish readers that haven&#8217;t heard it before.<\/p>\n<p>The good news is that a lot of this work was already done for me. Two years ago <a href=\"?p=43225\">I made a thing<\/a> in Unity. It was a sort of Minecraft clone, and the point of the project was to experiment with some alternate ways of generating caves. A wrote my Blender model importer back then, along with the atlas texture code I talked about <a href=\"?p=50872\">back in part 4<\/a>. I was able to drop both of these complex systems into this project and make use of them with only minor changes.<\/p>\n<p>Loots of programmers will jump in and say, &#8220;Duh, Shamus. That&#8217;s what code reuse is all about. You shouldn&#8217;t need to re-write everything all the time.&#8221;<\/p>\n<p>My problem is that code reuse is really hard when you&#8217;re trying to do graphics within barebones C++.\u00a0 In C++ you often find yourself in situations like this:<\/p>\n<ul>\n<li>The library I wanted to use for this project depends on this other, far larger library that I really don&#8217;t want to use.<\/li>\n<li>The library is incompatible with another library I&#8217;m trying to use.<\/li>\n<li>The library is very old, and on modern compilers it generates hundreds of annoying warning messages. I don&#8217;t want to suppress those warnings because suppressing warnings is like taking the batteries out of your smoke detector because it&#8217;s too sensitive and goes off when you make toast. At the same time, I don&#8217;t want to make hundreds of small edits to clean up these harmless warnings.<\/li>\n<li>The library is no longer available. The project fell apart, the website vanished, I don&#8217;t have the source, and the binaries I downloaded years ago are for 32 bit applications and I&#8217;m working in 64 bit now.<\/li>\n<li>The documentation for this library has always been a disaster, and I&#8217;m tired of trying to reverse-engineer everything just to figure out how to do something simple.<\/li>\n<li>This library is enormous and I really just need a few tiny features from it.<\/li>\n<li>This project was originally targeted at linux. A few years ago I somehow managed to get it to compile on Windows, but on the current version of Visual Studio it won&#8217;t, and I have no idea what I&#8217;m going wrong. All the answers on Stack Overflow are short, condescending answers telling people to <a href=\"https:\/\/en.wikipedia.org\/wiki\/RTFM#:~:text=RTFM%20is%20an%20initialism%20for,the%20product%20manual%20or%20documentation.\">RTFM<\/a>, which is now a dead link<span class='snote' title='2'>Seriously, I think SO should adopt a policy of &#8220;If the answer is simple, then just answer it yourself rather than being a smartass and posting a link. Links die, and questions on this site do not.<\/span>.<\/li>\n<li>This library is now too old to be useful. Maybe it&#8217;s a rendering engine that can only access the pre-2004 OpenGL features. Maybe it&#8217;s an input library that supports old 90s flight sticks but not modern console controllers. Maybe it&#8217;s an ancient UI library that can&#8217;t handle resolutions above 1600&#215;900. Whatever.<\/li>\n<\/ul>\n<p>We can file all of these problems under the broad heading of &#8220;rotten&#8221;. It was good once. It would still be good now if you were still doing things the way you did them a decade ago. But time has passed and this library has been left to rot. We need something new.<\/p>\n<p>As a result, everyone one of my homebrew projects would begin like this:<\/p>\n<p>Okay, let me drop in my atlas texture code from the last project. Oh no. This one is linked to a PNG library to load the textures, but that&#8217;s rotten. I replaced it with this other library. Hang on, that one can read PNG images but not write them. I can fix that with this other library, but that&#8217;s linked to another rotten library.<\/p>\n<p>So every project would begin with a day or so of trying to find a set of libraries that would play well together. It&#8217;s not fun.<\/p>\n<p>But here in Unity land, it all just works. I can drop my atlas code into a new project and have it instantly begin working.<\/p>\n<p>You can see where the love \/ hate relationship comes from. On one hand, I get access to the C# language of simplicity, code reuse, as well as the Unity ecosystem of types and tools. On the other hand, I have to struggle with the limitations of using a proprietary engine with frustrating holes in its documentation. Sometimes I really miss the power and speed of C++, but ultimately I think my own laziness is stronger than my desire for perfection.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In the previous entry, I talked about all the models I made in Blender. However, in that entry I kinda skipped over a step. Before I could import those models into my&#8230; game&#8230; demo&#8230; experiment&#8230; thing, I had to make my own importer, because the built-in Unity importer is a hot mess. It&#8217;s frustrating, because [&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-51107","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\/51107","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=51107"}],"version-history":[{"count":16,"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=\/wp\/v2\/posts\/51107\/revisions"}],"predecessor-version":[{"id":51148,"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=\/wp\/v2\/posts\/51107\/revisions\/51148"}],"wp:attachment":[{"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=51107"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=51107"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=51107"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}