
I love graphics programming

I love graphics programming! We all make mistakes in the process of designing and writing code. Sometimes these are errors of logic (when the algorithm is thought out inaccurately or not to the end), sometimes errors arising out of carelessness, and many, many more options. And what happens in a normal workflow? - There are no necessary entries in the lists, some numbers are considered incorrect, error messages fall out, and so on. In graphics programming, everything is a bit more fun, because often we get a result that simply does not meet the expected. In my small project, I decided to keep such “results” throughout the entire development process and would like to share them with you.
Anyone who does not like Android, Live Wallpaper, Minecraft, bicycles, a stream of consciousness that is loosely attached to the topic and all that, I want to immediately warn that the content of this post may upset them, so continue reading at your own peril and risk. I will also leave a warning here for users of mobile or simply unlimited Internet: a lot of pictures will follow .
Hello!
I am a software developer with many years of experience. This is not to say that my work is monotonous and boring. I solve problems of different levels, sometimes I come up with solutions, sometimes I myself implement them, sometimes I look for vulnerabilities in systems, and sometimes I just tell you why the monitor is not a computer. Perhaps it’s just that I’m not only engaged in coding that brings a bit of diversity and creativity to the workflow.
It also happens that due to various problems (the budget or development time is limited or just some of the higher-level cock pecked in the wrong direction), I have to look for ready-made solutions for many things, which turns my work into a children's game with dices , where it is required to “put a cube on a cube” and watch how the managers clap their hands. Perhaps this is the thing that turns my work into a routine (although yes, even in the moments of “playing with cubes”, I usually continue to say that the monitor is not a computer). In order to somehow escape the routine and not forget how to implement simple things, I like to program “everything in a row” in my free time. And I noticed that I love graphics programming ...
What are you talking about?
I'll start from the beginning, without much digression about who I am and why. Having bought a new phone (here the tendency of my articles on the hub is already looming ...), I wanted to set myself a “live wallpaper”, because I don’t know why, but I like it when something moves on the screen. Probably all because then I feel that I bought a phone with a four-core processor for a reason. I walked around the store, thought that I would like to see on the screen and decided that I want something in the style of minecraft, but unfortunately I did not find anything that would properly please me. Then I had the imprudence to decide to do everything myself ...
First doubts about this ...
It should be a little distracted from the main story and explain why I had doubts about the idea of "but I will write - if I can do something for myself." In school years, a game was installed on computers (as I recently learned, the game was called “Treasure” for BK-0010), where a white man (who should play for whom) was collecting something (in my memory it was exactly the keys, although as it turned out later, it should have been chests), and the black man really hated the white man for something and killed him with a touch. I don’t know why, but my thoughts about the game aroused nostalgic feelings for me, and so I decided “but I’ll write it myself.”
In order not to bore you with a story about the development process and other details (not about my story), I just describe the meaning:wrote, it worked exactly as I remembered, played once, quit, because already “played enough” in the process of debugging .
For those who are interested, the result is this: The

inner voice here also told me that I would not use the result of my work, because I’ve seen too much of him during the development period, but I, as usual, decided that “this time it definitely won’t happen” (self-persuasion force, huh). I’ll run ahead and say that the inner voice was right ...
Where is the graphics programming ?!
I'll try to return to the main story. I immediately decided that I would not use either OpenGL or anything else that would help me realize the task - only hardcore. Moreover, I was always interested in the interaction of Java code with native code for Android, and here also turned up a good opportunity to try my hand at solving this problem.
I immediately decided to check if I could even implement rendering with a sufficient frame rate, with a constant call to the native library. For verification, I implemented the following task - filling the screen with pictures with some arbitrary “shading” coefficient (essentially just with the brightness parameter, where the original picture is considered as bright as possible). Wrote a variant in Java and C ++. I ran both versions with a rough test of timing and saw that, on average, the C ++ version worked a little faster, even though Java itself did display the finished image “on the screen”. As pictures, I immediately took one of the nice, in my opinion, textures for minecraft, the result was something like this:

Since I decided from the very beginning that I will implement everything myself and I will not peek into the literature or seek help on the Internet, the edges of my image looked something like this:

Obviously, the images from which you should draw only a part are incorrectly processed, we fix it ...
So , the problem is being solved, which means you can get down to business and start looking for a solution and optimal implementation.
Since I chose a solution using JNI for implementation, as a result, the development was conducted in a mixed mode. I wrote and tested the basic logic under Windows, immediately generating an image for 10 screens (these are the wide images that will follow later in the article), and from time to time I checked the solution on the phone.
"Results"
So, the horizon (so far with random blocks, I don’t even know why I made it random - I am writing an article and am wondering myself): Large pictures are clickable, but habrastorage slightly reduced their size (the original was 7200 x 1280) Now from random garbage, we turn to meaningful content. The horizon line is “meaningful”: Next, it was necessary to create “caves” (indentations in the surface) so that the relief would not look so primitive. Because by that time the implementation was still raw, then the verification of the algorithm for creating caves was a drawing of “caves” with a different texture: The next step was to divide everything into foreground and background, and the background should have a darker texture: This already looks like something then, but still very far from the result.




I changed the textures, I decided to add a light source below - lava, but I made a mistake with the texture, so the bottom was “filled with torches”: I

correct and add another light source - torches:

I realized that the ratio of the foreground and background blocks does not suit me and changed the coefficients: Looking ahead, it should be noted that this is the part that I changed many times to achieve such a result, to calm down and not touch it anymore. The purpose of adding light sources was more adequate lighting - lighting from light sources. Light sources were divided into three groups:

- Lighting from the sky. The brightest source of light, but the change of time of day was originally conceived, which means that lighting from the sky depends on time.
- Lava lighting. A less bright source of light than the sky during the day, but the brightness does not change over time.
- Lighting from torches. The least bright light source. The brightness is also constant.
As a result, two parameters began to influence the lighting of the block - the distance to the sky and the distance from the static light source: On the left, the light propagation from the sources, and on the right simply “the background is darker than the foreground”. Here transparency began to beg, because you can not leave torches with a white background. To avoid the questions “what’s the problem?”, I’ll remind you that I have an array of some digits (pixels) and other digits (also pixels), and all the rules for transferring and drawing still needed to be written. We wanted transparency - here you have transparency (a little more transparent and it would be necessary to draw a picture from the phone’s camera so that it is “transparent” enough): Correcting ... Correcting the background, accidentally “painted over” the caves with ground:



I know that it may seem that some of this was done intentionally, but I assure you that all the results were obtained by chance. In truth, I’m even at a loss to say how I did it, but the result is what it is. We also fix this ... The
illumination of the block behind the torch is calculated incorrectly (block after torch is darker than the neighboring blocks): The

error is rather stupid, but somehow I didn’t think at all that the torch could be in a well-lit place and considered the illumination of the block to be the maximum brightness value torch. There could be at least two solutions - to fix the lighting or to remove the torches from the illuminated places. I decided to fix the lighting.
Now I decided that it was necessary to make it possible to specify a string (seed) that defines a unique “map”, which means I needed my own implementation of generating random numbers (I really didn’t need it, because regular rand would have been enough, but the bike asked): and it turned out rather “by accident", to say the least. The next step, additional blocks were added (I miss this step, excuse me, and there are so many pictures) to diversify the appearance of the relief and then the turn of trees came.


About the trees, I want to say a few words separately ... By the middle of the development process, I already had a comment in the code, several notes on paper and one note for the screenshots, something like this: “i h8 3s”. And there were reasons for that. The trees immediately went somehow difficult. Every little thing, every edit of the code necessarily affected the trees. In general, no matter how ridiculous it may sound, it was the trees that were the biggest splinter.
So, the first iteration of torment with trees:

To begin with, it was decided to make a tree trunk and leaves above the trunk (so far there are no leaves on the sides), but even here I mixed up the blocks and got an inverted result.
I fixed the error and added leaves on the sides, and again the wrong block:

As a result, after changing the number of trees, this is the result:

Then I decided that the algorithm for generating the horizon line upsets me and that it would be nice to fix it, which I did. The result was quite predictable - nothing good: Then a quick-fix, usual for many developers, followed, without much understanding of the essence of the problem (after all, I just wrote this code, it is obvious that I can fix it without hesitation!), Which, like it could be assumed that this did not lead to a positive result: It is rather curious that the horizon is not completely flat, but has a slight distortion at the beginning. As a result, I experimented with different parameters and after many results calmed down and stopped thinking about corrections for the horizon line. Here are some results for the skyline:



After I got a working prototype, it's time to debug it, clean the code and do the optimization. I started by getting rid of magic numbers, of which there were about 10, which linked most of the parameters to the size of the screen and blocks at the time of the tests.
And the trees continued to upset everything - it was necessary to make sure that they were generated only on the ground, for the volume I added a shade on some blocks of leaves (which by the way also did not work as I would like by that time):

It became clear that we needed to debug the drawing function shaded blocks, and at the same time optimize something in it. Again a quick fix of “errors”, again a rather funny result:

Here, it almost instantly “dawned on me”, what I did wrong and the new correction did not take long: Here it should be noted right away that usually I don’t write code in this style (that is, in the style of “I write first, then I think” ) But in this project, I found it very funny. After all, my every mistake, every stupidity, necessarily led to a result, and very rarely I could predict this result or immediately explain “why.”

At this point, the texture of the leaves and grass (ground with grass) was a certain green color. An implementation was requested that would allow us to change color, allowing us to change the season with little blood. Yes, I know very well that this could be easily done in Java and not come up with anything, but the sports interest was too strong. To do this, the texture was changed and a function was written to “paint the texture”: the

function itself had to be corrected several times, because she created the effect of a multi-caliber whale (two images in the middle), but did not do what is necessary (the right image). Here are some results of the algorithm:

From time to time, I ruled certain methods to tidy up the code and from time to time received a variety of results. Another example:

From time to time, quite amusing situations from the world of debugging surfaced. For example, a mistake in drawing a gradient (another generator of multi-colored stripes):

During debugging of this error, when I was already running out of ideas, the debugger gave this color value:

On that day, I left the debugging and decided to rest so that the debugger stopped talking about bad food quality.
At this point, I decided to start simultaneously testing the result on the phone. The order of colors on the phone turned out to be slightly different, so the red and blue component colors changed places:

By the way, I forgot once again to remind myself that I hate trees ... Trees behaved strangely at night:

В это время, у телефона вообще был свой собственный взгляд на то, как следует рисовать картинку после смещения (скролинга пальцем):


Ладно, цвет на картинке слева такой, потому что я забыл про положение синей и красной составляющей, а вот модный эффект motion blur — это уже “спасибо” android за то, что он совершенно верно рисовал моё изображение, у которого я не подумал про альфа канал (в альфа канале к тому моменту могло быть всё что угодно).
Кстати! Давно я не показывал Вам свои деревья! Вот:

Проблем тут довольно много — и неверное освещение некоторых блоков листьев, и неверная прорисовка “прозрачных пикселей”.
In parallel, I began work on a system of waypoints or, in simple terms, an algorithm for finding paths. The path was needed so that it was possible to add zombies and other characters and at the same time would not have to calculate their behavior several steps forward each time (so that they did not stupid in place). For myself, I began to mark the paths visually in order to assess the quality of the algorithm: A more advanced version of the visualization looked like this: By the way, as a result, I did not add zombies (I didn’t get my hands on it), but I debugged the waypoint system. Pay attention to the trees in these two pictures. The trees still looked great ... At some point, when I was trying to fix the appearance of the trees, I got another “positive":



Here are some more interesting bugs from the phone that are directly related to transparency (alpha channel):







The last thing that needed to be implemented was the effect of volume (the edges of the blocks) so that the picture was not so flat and simple. For the faces, it was necessary to write two algorithms - the algorithm for resizing the image and the algorithm for tilting the image.
I'll start with the algorithm for reducing the size of the picture:

From top to bottom - the results of the algorithm. At the very bottom - the desired result (the test was carried out in a large picture, for clarity).
The rotation algorithm was more distinguished:

at the top left is the initial image, and at the bottom right is the desired result, and in the middle is everything that turned out on the way to the result.
When the algorithms were ready, making the volume was already quite simple. The block was made up of 3 faces (look from one side, pseudo-3D or the so-called 2.5d). For beauty, a linear gradient was applied to the faces, which also had to be debugged to get the desired result:

Since I could not determine the size of the block faces, I made it possible to change this parameter:

After all the changes, it was necessary to optimize the drawing process so as not to draw nothing of the need to draw. The result of this optimization was again a problem with the transparency channel that surfaced during the tests on the phone. I must admit that the result obtained looked rather curious:



Summary
In the end, all critical errors were debugged, added textures and buns in the form of effects. Two versions for the store were made - free (very simplified) and paid (with all the goodies).
I decided not to even raise the technical side in this article, because and so I’m not sure that someone will finish it to the end. If people who have mastered this article will be interested to learn about the technical side of this story, then write in the comments or in PM, and I will definitely tell you about the difficulties I encountered, the implementation features and other details.
I hope that this will push someone else to create something with their own hands, and not just using ready-made frameworks and libraries.
Thanks to those who even leafed through to the end of the article!