Mechanic #201 - PGC: Low-Res Stretching|
|Posted: Apr 10, 2014|
A simple way to control how the interior of a low resolution rectangle grows and shrinks.
Fig 201.1 - Pixel art at 1x, 1.5x, and 2x scale.
One of the unspoken flaws of pixel art is that, because it is low resolution, it only looks good when it is scaled by whole numbers. That is, pixel art looks fine at 2x, 3x, 4x, etc, but looks like barf when scaled by anything in between. At low resolutions, the difference of a single pixel can fundamentally change the nature of the work. At 1.5x scale, half the pixels are 1x1 and half are 2x2, producing the previously mentioned barf.
The root cause is rounding errors. If you think of like converting a floating point to an integer, each pixel in the resulting image is actually a fraction of a pixel that is rounded to the nearest integer. In some cases, this produces pixel fractions that are large enough to dominate two pixels on screen.
Why am I talking about pixel art? Because the same thing applies to whenever you are scaling ANYTHING that is low resolution, including something like [#167 - PGC: Interior Design Regions]. If you are building a Zelda-like map, which is no more than 16x16 rooms, the problem of stretching these zones is very much like trying to scale pixel art. Works fine if you double everything, but if you are only adding a new column, how do you control which column to add?
Stretching by Columns and Rows|
This entry is based on a few basic premises:
* You are working in low resolution space, such that fractional sizes are not possible.
* The thing you are shrinking/stretching is a rectangle.
* Stretching is a matter of repeating columns/rows, while shrinking is a matter of removing them.
* You want to control the order that columns/rows are added or removed.
Fig 201.2 - Stretch vertically by doubling a row.
The simplest scenario is that you have a rectangle of cells, with each cell belonging to a specific zone (like castle, forest, dungeon, etc). When you want to stretch it one row up, you fill in the new, empty row by repeating one of the neighboring rows - in this case, the row below it.
There's two decisions being made here. Where the new empty row is added, and which row is copied into it. When scaling pixel art, these decisions are made based on a linear distribution of pixels. The new empty rows are evenly distributed amongst the height of the rectangle, and which cells are inserted into the empty rows is based on stretching the image out and rounding the fractions out.
We don't want to do that because we do not necessarily want to stretch the rectangle linearly, and not just because it can produce unpredictable results. We want more control than that because we want some zones to grow more easily than others. Why that is the case will be more apparent when I talk about shrinking zones and rows that don't grow or grow more quickly than others. This is just the simplest scenario, so I can explain the basic process.
Fig 201.3 - Row/Column priorities dictate scaling.
The basic process is to assign each row and column a priority number. This will indicate the order in which that column/row is repeated. For instance, a row with a priority of 4 will only be duplicated if the rectangle is vertically stretched at least 4 rows.
In the above example, a 4x4 rectangle is stretched into a 6x7 rectangle - not large enough to double the size. The rows and columns are duplicated in order, so if the rectangle grew by a row, row 4 would now be duplicated. If it grew by a column, column 3 would be duplicated.
The final interior can be calculated simply. Divide the number of rows by the number of rows in the unstretched rectangle. Duplicate each row a number of times equal to the quotient, and once again if the row priority is equal to or less than the remainder. So when 4 becomes 7, the quotient is 1 (each row only shows up once by default) and the remainder is 3, which means that rows 1-3 are duplicated. When 4 because 9, the quotient is 2 (each row is duplicated by default) and the remainder 1, which means that row 1 shows up three times.
In that simple case, the rectangle ultimately grows linearly, as in, each row is duplicated at least once before any one row is duplicated again. Sometimes, this is not what we want. For example, sometimes, we want lines to not be duplicated at all (such as the rectangle's borders) or we want lines to be duplicated more than one before the the cycle starts anew. This is accomplished by rows with zero priority, or multiple priorities.
Fig 201.4 - Skyscraper zone with nonlinear scaling.
Let's say you want to build something like a skyscraper or a tall castle. In this case, you might not want to repeat rows. For instance, the ground floor and roof will always be a single row at the top and bottom, respectively. There's also a specific row roughly in the middle that you don't want to grow. In addition to that, you want the bottom half of the skyscraper to grow twice as quickly as the top half.
The process is almost identical. The main difference is how you count totals. Instead of counting rows for your division, only count priority numbers. If a row has more than one priority number, count it multiple times.
Fig 201.5 - Skyscraper zone with nonlinear scaling.
Here, the rectangle grows from 3x5 into a 5x8. For the rows, instead of counting all the rows, only count the priority numbers, so you division isn't 8 divided by 5, but instead 5 divided by 3. This gives you a quotient of 1 with a remainder of 2. Starting at the top, left corner, each X row is shown only once, and rows 1-2 are duplicated. For columns, it's 3 divided by 1, so the X columns appear once, and the 1 column is repeated 3 times (quotient of 3, remainder of 0).
Shrinking Through Removal|
The main problem with this approach is that it requires all the rows/columns to be there that will ultimately be repeated. This means that the minimum size rectangle you need to specify could actually be larger than the minimum size rectangle you allow. For instance, in the skyscraper example about, you need a minimum rectangle of 3x5 to define the stretch parameters, but it could be possible that a 2x2 skyscraper is possible by removing specific rows and columns.
One way to solve this problem is to simply provide multiple zone templates for these sizes. If a skyscraper has a size less than 3x5, use the 2x2 zone template instead. This could be useful for many reasons, such as using a completely different layout if the skyscraper is only 1 cell wide.
Fig 201.6 - Removal priorities.
Another solution is to use inverse priorities. Instead of telling you which columns or rows to duplicate first, it tells you which columns or rows to omit first. Basically, remove them in priority order, with the minimum size being equal to the number of lines with X removal priorites.