With the story done, I’m at the stage of building all the art and dialogue to execute it. But I also can’t help continuing to refine the imaging engine... As if it wasn’t complex enough already, I’ve added one more layer!
When the brightness is cranked too light or too dark for the region you’re viewing, large areas can go to fully black or white. That’s OK most of the time—but if you ALSO zoom in really far (32x or 64x) then those areas can be really large: you can sometimes get a fully empty black or white screen, or close to it.
At that point, you can’t even see when you pan/scroll with the d-pad. No big deal, obviously you need to crank the image brighter or darker! But I just I didn’t like the look and feel of those blank open areas.
_
Solution: add a little grain to the image, only visible at the highest zooms.
It uses kDrawModeXOR
so it works on any underlying graphics.
That way there’s a hint of texture you can see scroll. It also just looks nice I think! And it doesn’t harm the image: zooming in that far is mainly for fun anyway, and the grain is pretty slight.
But I didn’t want repeating grain. If it’s all/most of what you’re looking at at those moments, then I didn’t want you to see the same cluster of dots keep going by at the same interval all lined up in a row.
Making the scrolling grain image large is a start: twice the dimensions of the screen. But it would still repeat, just less obviously.
To test this, I temporarily added a bold test doodle to my grain image. (It’s actually a quarter of the full grain image but I repeated it 4x.)
That’s the repetition I did not want.
_
Solution: randomized grain.
Originally I was going to randomly flip each instance of the grain, and/or have a table of multiple different grain images (as if I’m not pushing RAM enough already). There would still be entire screens of repeating grain sometimes, but much less often.
That’s easy—just pick a random index 1 to 4 from this table:
flips = {gfx.kImageUnflipped, gfx.kImageFlippedX, gfx.kImageFlippedY, gfx.kImageFlippedXY}
But luckily, since my engine only redraws the newly-revealed portion of the screen as you pan, I have a way to do even better: randomize the flip of each new strip that is rendered.
Now you’re talking! One source image, endless random grain.
Just one problem: as you pan, my engine pre-renders a whole range of different brightness levels. That’s what keeps cranking fast and smooth. But it means you now have random grain that changes as you tune the brightness. This gave the whole screen a distracting animated “sparkle” effect. It looked neat, but it looked very wrong.
_
Solution: deterministic pseudo-random grain.
I thought, what if I generated the flip number 1 to 4 from the pan coordinates with no random component?
Modulo (division remainder, the %
operator) can easily turn any positive number into a number 0 to 3. Add 1 and I have my flip table index that changes with panning on either axis:
flips[(viewX + viewY) % 4 + 1]
No more sparkle! But how does it look?
It turned out the coordinates alone produced a repeating result horizontally (just the luck of how the numbers happened to divide). All I had to do to fix that was add a fixed integer (say, 3) to the coordinate sum before the modulo.
Then it looked random! However, the grain also did not change when zooming between 32x and 64x. Again, it felt wrong.
So in addition to a fixed integer, I also added the index of the current zoom level. Now there was a non-repeating result that looked random, didn’t sparkle, and was unique at different zooms.
flips[(viewX + viewY + 3 + zoomNum) % 4 + 1]
Success! Completely deterministic, fast, and with repetition being infrequent enough to satisfy me.
Remove the test doodle, and here’s the grain alone: