A Bit of Housekeeping

It’s been a long time since I made any serious effort on the holy grail task of getting a cube on the screen.  For that reason, everything I was trying to use to do it is now very out of date.  This problem resulted in a bit of housekeeping.  Since I’m in there updating tools and reading over code I’d written, I figured it was also a good time to grab the latest collection of libraries and assorted goodies and make sure that the new versions didn’t break anything.  Specifically, I believe it is time to bump LWJGL up to the latest version, and to seriously consider a switch from the main line of Java to OpenJDK.

I’ve been coding on Java 8 for a very long time.  I had everything I needed, and Oracle kept updating it with bug fixes and other useful patches, so I really didn’t have any need to change.  Recently however, Oracle has gotten onto a pattern of releasing major versions of Java much much faster than before.  In fact, since I last posted, the official JDK has advanced all the way to version 11.  With this new pattern of fast releases, they also appear to have changed the license agreement in a way that I don’t care for.

If I’m reading or understanding their new license correctly, they are making it pointlessly expensive to use Java to create commercial software.  I hope I’m just not understanding the intent of the new license, because if I’m right, it seems like Oracle is essentially trying to make Java itself go away by pricing themselves out of a market with dozens of other free options available.  As a coder that prefers Java to all other languages, this makes me rather sad and was the driving force behind my idea to switch to OpenJDK for QubeKwest.  With any luck, it’ll be pretty straightforward to do this, and unless I’m misunderstanding again, the use of OpenJDK dodges this entire license issue completely.

Starting to Itch

A friend of mine has been talking a lot about a game project he’s been working on lately.  In general, I love talking about coding projects, even when they aren’t mine.  I find it sets my mind spinning in fun and interesting ways.  When I’m not talking about one of my own projects, I mostly hope that I am a useful source of ideas for the person I’m listening to.  There is another side effect though.

Talking about other projects, and sharing in the excitement that other people have for their projects is great, but I end up missing my own projects that have stalled.  I describe this longing for my own coding projects as “itching to work on them.”  Thankfully there isn’t any actual physical itching involved, but conversations spawn ideas.  Ideas in turn spawn problem solving.  Problem solving causes a desire to put these theoretical solutions to code.

The last time I posted anything here was nearly a year ago now.  In that time I’ve worked on several other projects and as a rule, I’ve stalled out on all of them.  I’m not sure why I do this, but I think it has to do with getting to a part of the code that is hard or boring or otherwise just not something I want to work on.  The solution is to simply stop until I wish I was working on it again.  That time is fast approaching again.

To that end, I’ve updated my development tools to their latest versions for probably the 3rd or 4th time since I stopped work on QubeKwest.  I’ve done some thinking about my library choices and I’ve devoted enough time picking over the code I’d already written that I found some things to correct about my project documentation.  The itch is returning in full force, and I expect to resume working on the project soon.  Hopefully, “soon” really means soon this time.

Educational Detour

Back when I was first trying to get a cube on the screen, and for all the other attempts to do the same thing that followed I suppose, there were astonishingly few useful resources for LWJGL education.  Searching Amazon even today results in exactly zero books on the subject.  That meant a rather tricky prospect of attempting to learn OpenGL and converting as you go from C++ to Java with LWJGL.  Needless to say, if one learning curve is tough, having a couple at once is even worse.

Having now taken a bunch of time off and come back to it, some things have changed.  New versions of Java and LWJGL are out.  Vulkan has gone from being a fun theory about the future of graphics to a real thing that people all over are now using.  With this in mind, it seemed like a good idea to search online for tutorials about LWJGL and Vulcan again.  The good news is that they now both exist.  The bad news is that now that they exist, I have to stop what I’m doing with QubeKwest and buckle down to learn them.  Technically I don’t have to stop work on QubeKwest and I could simply attempt to integrate my new knowledge into the game as I go.  This will create a massive distraction however so I have chosen to step to the side and work on the learning as a completely independent handful of projects.  When tackling learning curves it’s often better to simplify.

Vulkan is a new and shiny way of doing graphics things (and computing things, but those are less important to me right now) and is by the same group that made OpenGL.  That means that Vulkan follows a lot of the same principles as OpenGL does.  That’s probably on purpose so that the hordes of people that have been using OpenGL for the last 25+ years can move over to Vulkan relatively seamlessly.

Just like with OpenGL, everything with Vulkan is typically in C++.  That once again meant that I’d be forced to do a bit of learning while translating at the same time.  For that reason, I’ve decided to ignore the existence of Vulkan for the moment (even though I think it’s super cool and really want to learn it) and to instead focus on the LWJGL Tutorial I found which uses OpenGL.  Still sort of more than one learning curve, but it seems like it’ll be the most straightforward approach.  The tutorial is a roughly 400 page book so I’m pretty sure it’ll take me quite a while to go through.

Eventually, once I’ve completed the LWJGL Tutorial, I will probably try to integrate the new knowledge into QubeKwest before taking on Vulkan.  I have a couple of real books about Vulkan and a Vulkan Tutorial I found online to use to tackle that task when it comes.  If the posts end up being a little farther apart for a while it’s because I’m working my way through things that have no solid connection to QubeKwest.  Once I start trying to weave new understandings of things into QubeKwest I’ll resume posting about the progress on that front.

Block Data Reorganization

Now that I’m actually trying to use blocks a little bit, I’ve realized they needed a bit of changing from my earlier theories.  The changes had the convenient side effect of shrinking the amount of data stored in each block by a little, so I can use a little less memory in the game and a decent amount less on the disk.  However, due to the prolific number of references in the code to blocks (the smallest piece of the world) there were a ton of places that needed to be changed to line up with this new arrangement of information.

The fields on a block when I started this process were laid out like this:

short blockType;  // 2 bytes
byte variant;     // 1 byte
byte spatial;     // 1 byte
long extra;       // 8 bytes

These combined to be a 12 byte block with loads of space in the extra area to store fancy bits.

The problem was that almost immediately I came up with a couple of spatial arrangements that wouldn’t fit in the space I’d provided for spatial data.  This made me want to increase the spatial data from 1 byte to 2.  It’s worth pointing out that every byte on a block takes up roughly 4096 bytes in an in-memory chunk, or over 2MB in a fully realized cluster.  That means making a block bigger is something that deserves a bit of pondering before you just start throwing around bytes.

Next I considered the variant data and realized that it is a seldom used data value for when there are distinct parts of the same type of block.  In other words almost none of the normal types of blocks will ever have a variant.  That makes it a waste of 8 bits of storage almost all the time.

Most blocks that are related are simply different block types.  For example, oak wood and oak leaves are two different types of block, not two variants of oak.  A good example of a block with variants is a bed.  When you place a bed it takes up multiple blocks in space but all of the blocks occupied are still bed blocks.  For narrow beds there are the Head and the Foot variants and for a wide bed there are the Head Left, Head Right, Foot Left, and Foot Right variants.  What that boils down to is that I can move the variant information into the extra data area and free up that mostly wasted byte.

Next I considered tinting.  This is the fun ability to make a torch light up blue instead of yellow or to create fancy colored sheep.  This information needs to be present on nearly every single type of block and yet it currently lives in the extra data area.  That chews up 12 bits of the extra data area almost all the time.  That’s not really a problem, but it’s a pretty big chunk of extra data that’s really there all the time.  That means that the tint should really be moved into its own field on the block.

Tinting data is a little bit wasteful because the new field for it on a block is 16 bits and the tinting itself is 12 bits, but at the end of the day wasting 4 bits for tinting all the time is better than wasting 8 bits almost all the time for variant information that’s almost never used.

Next I moved on to the extra data area.  I came up with an idea of making a sort of extra data overflow that lives in the chunk.  This data space means that there is less concern about running out of bits in the extra data area and just being stuck.  With that in mind I decided to shrink the extra data area from 8 bytes to 4 bytes.

That means that the new layout of the fields on a block are:

short blockType;  // 2 bytes 
short tint;       // 2 bytes 
short spatial;    // 2 bytes
int extra;        // 4 bytes

This new layout gives me more useful spatial data space, turns tint into a first class citizen, pushes the variant information into the extra data area, and shrinks the extra data area with the option of chunk level expansion.  That means that I can now fit a block in only 10 bytes.

I like this arrangement better, but as with anything else, we’ll just have to see how it plays out and whether or not I’ll end up making more changes.

Block Spatial Data

Apparently both “spatial” and “spacial” are valid ways of spelling the word that describes things that exist or occur in space.  I’ve chosen to use the “T” even though my fingers often choose the “C” for some reason.  I think it’s because part of me thinks I’m spelling “special” or something.  In the future if anyone is looking over the JavaDocs for the project and sees it spelled in different ways, I apologize for that in advance.

When I first started the project I spent a bit of time trying to determine the different ways of placing a block in its spot of space.  At first glance there aren’t any ways of laying blocks in space.  They are simply there, or they aren’t (which means air is there instead, which is still a block).  Upon thinking about it some more, there are bunches of things that need more information.  Thus, back when I created the Block data structure, I left 8 bits for holding spatial information.  The problem at the time was that I wasn’t sure how that data would be used, and that’s what I spent my time over the last few days trying to figure out.

The first thing I thought of was torches.  They get mounted inside the space of a block but sometimes you feel like you need more than one in single block to make things look right.  Even though torches actually appear to be mounted to another block next to where they are, that’s not really what is happening.  The problem is that you spend a lot of time trying to make your torches look like they were placed the way they are on purpose and then you get to a narrow place where there is one empty block that needs a couple of torches to finish the pattern properly.

The torch problem inspired the concept of placing multiple identical wall mounted items in a single block.  To orchestrate that, the first type of spatial data captures which of the 6 “walls” of a block have something mounted to them.  With that information handy, there is now no restriction on there being more than one torch in a block and you can complete your patterns as accurately as you want.  You must keep in mind that this doesn’t allow a block to consist of multiple different types of material.  For example, if a block is a torch block, it can have multiple torches, but it can’t have both a torch and a ladder.

The next thing that came to mind was trees, or more specifically, wood blocks.  If you picture a tree or branch after it has been cut off, there are rings and the edge of bark visible on the side that was cut, but from the other sides all you see is bark.  To a world made up of blocks, there are only three ways a piece of wood that looks like a tree can be facing.  That means they are aligned with a single axis of the world.  For example, logs that are lined up with the Y axis and then stacked on top of each other would resemble a tree.  If you line them up with either the X or the Z axis, you get something that looks like a log laying on the ground or a branch going horizontally.

This type of spatial data is called axis alignment and allows things like a block of wood to be placed facing whichever direction you want.  Related to this, something like a jack-o-lantern carved up for Halloween works the same way, but it also needs information to specify which way along the axis it’s actually facing since only one side actually has the face on it.  This information is also included in the axis alignment.

Next I started thinking about ways to make things decorative, and the easiest way to improve how nice things can look is to provide more options for how a block can look.  The two ideas I had were what I called slices and microblocks.  Both concepts allows a block to appear to be less than whole and give you a lot of flexibility for earning those presentation points.

Slices represent a block cut into 8 slices like a stack of pancakes.  These can be aligned along any axis in the world so they are only really a stack of pancakes when they are aligned on the Y axis, but you get the idea.  These can be created within world space with special tools.  I’m thinking something like a saw would allow you to make these.

Microblocks represent a block cut into 8 smaller cubes in a 2x2x2 arrangement.  As with things like torches, you can’t have these be made of more than one material.  If you are dealing with microblocks made of rock in a single block, then all of them are made of rock.  Like slices, these can also be created in the world with special tools.  A tool like a chisel should do nicely.

Next I thought about water and how it flows.  For this one, I’m not sure yet how I will approach it.  Is flowing water that is near its limit of how far it can flow spatial data?  Is it a variant of water that contains information about how it is flowing?  Perhaps it’s special properties of flowing things that live in the extra data area of a block.  I have lots of choices and I’ll have to figure it out eventually, but this is not that time.  Things that flow will likely be a full production release to add it, so I figure I can worry about it later.

Related to flowing things and microblocks, there is at least some chance that I will revise the spatial data to be 16 bits instead of only 8.  I don’t love this concept because it makes every single block a whole byte bigger.  While that may seem like no big deal, it means that a fully populated cluster just grew by over two million bytes, so it’s not a decision I make lightly.  Perhaps I can come up with something to make this not as horrible.

And just like that, I’ve got concepts of how to lay things out in the world (no matter how you spell spatial) with a bit more finesse.  I’ve got more things to think about and more still to code, but at least I’ve started to unravel this particular ball of string.