Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.

Adds an app to simulate moss on the go.
Features
Images
Usage
A bit of text is shown to introduce each moss. Tap to continue. Let the moss grow, then drag your finger over the screen to eat it. Rinse and repeat. (Rinsing optional, you can overwater mosses.)
Details
There are four normal scenes in the moss simulator: Forest, Cave, Civilization, and House. After each moss, there is a chance to move to a neighboring scene. They are connected as such:
Cave <-> Forest <-> Civilization <-> House
Forest and Civilization share some mosses, but otherwise almost all mosses are unique to their scene. There are some rare mosses that can be found anywhere as well.
There is a debug scene select mode that is accessible by holding tap on the app icon when launching, and continuing to hold for at least 1 second after it launches (the screen will stay dark during this). When released, press the side button to cycle the selected scene. Tap the screen again to accept.
The debug scene select mode exposes two (or so) otherwise inaccessible scenes: Error and DbgAllMoss. Error is a catch-all and only contains a failsafe moss. DbgAllMoss goes through every moss in order, showing its index in the inbetween texts. There's something at the end of DbgAllMoss if you munch your way to it.
This app does not work in Infiniemu due to an 'unhandled exception vcmpe.f32'. It works correctly in Infinisim, but generation takes a consistent amount of time (texture generation is much faster, but screen drawing is slow) and eating is slow.
This app prevents the screen from sleeping as long as it is open.
The TextureGenerator object is by far the most interesting component in this app. All moss images are created using it.
TextureGenerator usage
TextureGenerator
The topmost object in this structure.
It contains only a single vector of TextureLayer objects, which will be described next. These TextureLayers describe each layer to put onto the image. So if you want some nice multilayered Perlin noise, you'd simply make several Perlin layers of decreasing scales.
To get the image out of it, use
GetBlock(). You pass in a buffer oflv_coord_tand the bounds of the area you want. This portion of the texture is generated and put into the buffer, starting at index 0. The bounds are inclusive, so make sure your buffer is of adequate size!You may additionally use
GetPixel()on the TextureGenerator butGetBlock()is preferred since it forces a structure which allows Perlin noise generation to run faster (caches some useful values).To add layers, pass a
TextureLayerintoAddTextureLayer(). The TextureLayer is copied when passed in, so you may reuse and modify a single TextureLayer for multiple similar layers.See header file for exact functions and parameter explanations.
TextureLayer
The descriptors for each layer.
This consists primarily of a
NoiseType(an enum describing what type this TextureLayer is) and aTextureLayerData[something]. The constructor requests anstd::anywhich MUST be the correct TextureLayerData* type for the NoiseType, else anstd::any_castexception will occur when the layer is rendered.The associations between NoiseType and TextureLayerData* are straightforward:
Blank->TextureLayerDataBlankSimple->TextureLayerDataSimplePerlin->TextureLayerDataPerlinShapeSquare->TextureLayerDataSquareShapeTriangle->TextureLayerDataTriangleShapeCircle->TextureLayerDataCircleThe TextureLayerData* objects will described in the next section.
You can set the bounds of what is being rendered with the chainable function
SetBounds(). This sets the min and max pixel to render IN SCREENSPACE. This has nothing to do with the offsets in the TextureLayerData* objects. If a pixel is outside the bounds (note that bounds are inclusive), the underlying texture will just not be calculated, so it can be used as an optimization tool as well as a tool for creating interesting visuals.You should not need to get the values from any TextureLayers. However, the appropriate functions to do so would be
CalculateLayer()andCalculatePixel(). They are similar to the TextureGenerator functionsGetBlock()andGetPixel(), except CalculateLayer updates the current pixels in the buffer (taking opacity into account) rather than overwriting anything.These objects are mostly used as a unified interface to all the different noise types there are in this generator system (hence the awkward use of std::any).
TextureLayerData*
These are the objects describing the parameters to each noise type.
Each layer type (except Blank) generates a value 0.0 - 1.0 for every pixel on the screen (depending on paramters), and uses that to get a value from a gradient described by a GradientData object (described in the next section).
TextureLayerDataBlank
TextureLayerDataBlankis the simplest, taking only a color and opacity. It fills in the entire screen with the given color. It is recommended to use a blank layer with full opacity as the bottommost layer as the background color.Example code:
TextureLayerDataSimple
TextureLayerDataSimplegenerates a random value 0.0-1.0 for every pixel on screen, and interpolates the GradientData based on that.Example code:
The below noise types all can be scaled and shifted separately on both the X and Y axes (since they are all deterministic). By default, the objects are created with identical scaling on X and Y, but this may be overridden with the chainable
SetScale()function. The X and Y shifts are randomly generated upon object creation, but may be overridden with the chainableSetShift()function.IMPORTANT: If the scale on an axis does not divide evenly into 65535, there will be a visible seam between pixels -32768 and 32767. All functions that set shift take this into account and if this seam would be visible, the screen's width/height (depending on axis) will be added to the shift. This can be an issue if using layers at slight offsets to each other, because if one would show the seam and another doesn't, the one that would show the seam will get shifted way off. An easy way to make a random offset that comfortably sits within the valid range is using
std::rand() & 0x3FFF.TextureLayerDataPerlin
TextureLayerDataPerlingenerates Perlin noise. It is set seed, so shifts must be used to mimic random contents. Useful for organic looking things (like, well, moss). Useful tidbit: One quarter of pixels will be in range (0.4, 0.5) and another quarter will be in range (0.0, 0.4). This mirrors across 0.5.Example code:
TextureLayerDataSquare
TextureLayerDataSquareis a distance function. It generates a grid pattern inside the X and Y scale, and effectively makes a square gradient coming out of the center of the square. Since it's just generating a value for each pixel, size isn't guaranteed and even if you give a very precise value for the start and end of the gradient, it could be a pixel off. Double check images in Infinisim if you need precise placement.Example code:
TextureLayerDataTriangle
TextureLayerDataTriangleis similar to TextureLayerDataSquare, except it generates triangles instead of squares. The gradient starts at 0.0 in the bottom middle (which is the base of the triangle) and increases as it goes to the top left and right corners. For perfect repeating triangles, the right side of the gradient should be at 0.5. For an equilateral triangle, X scale must equal Y scale * tan(30deg) * 2. Useful for squiggles and really any sort of sloped lines.Example code:
TextureLayerDataCircle
TextureLayerDataCircleis once again similar to TextureLayerDataSquare, but with a circle in a square. The gradient is circular of course, with 0.0 being the center and 1.0 being the outer four corners. For a perfect repeating circle inset in the square grid, the right side of the gradient should be at 1/sqrt(2) (use1.f / std::numbers::sqrt2_v<float>(or just 0.7071 if you don't care for specificity)).Example code:
GradientData
This is the most important object in the TextureGenerator, as it defines the colors outputted for each noise type.
Each noise type returns a value in range [0.0, 1.0] for every pixel, the GradientData maps those values to usable colors to be painted.
A gradient is a linear RGB interpolation between the left and right colors and alphas (aka to and from, or low and high respectively). So if you have the left side be RGB green and the right side be RGB red, the middle will look brown. For values that lie OUTSIDE of the linear interpolation section, they are clamped to the nearer value (lower than the low endpoint will just be the low endpoint's color). This behavior can be changed with the
SetClip(bool, bool)function. That sets if the left and right sides respectively should continue this behavior (false) or if they should be clipped to transparent (true). If an endpoint is at 0.0 or 1.0, I recommend setting the clip on that endpoint to false.There are helper functions to reset the endpoint locations, colors, and alphas. There are also variants of the color and alpha resetting functions which only take a single value, and these set BOTH endpoints to the passed color/alpha. Having both endpoints be the same color/alpha is slightly faster than having them different, but I don't have any reason to believe that's an actual performance bottleneck.
Examples:
See header file for exact functions and parameter explanations.
Whew, glad that explanation's over with.
Known issues
Receiving a notification while the app is running exits the app and loses all progress.
Final Notes
I am a well-adjusted person who can be trusted around moss.
(Please believe me.)