The goal of the changes made in this PR is to create something with this
effect ("Jonko" is in in green, the rest is in white):
"Dear god." Jonko quickly replied,
crestfallen.
In order to do this, I need to call `NE_RichTextRender3D` or
`NE_RichTextRender3DAlpha` three times: once to draw the first part of
the sentence with the white font, once to draw "Jonko" in turquoise, and
once to draw the remainder of the sentence in white again. In order to
ensure that text is being drawn in the correct location, I need the
position where the previous call stopped drawing, which leads to the
first change this PR makes: I have added a version of
`DSF_StringRenderDryRun` which accepts pointers to `final_x` and
`final_y` and stores the last position of `font->pointer_x` and
`font->pointer_y` in them and I have added a new variant of
`NE_RichTextRenderDryRun` called `NE_RichTextRenderDryRunWithPos` which
uses that DSF function and has analogous parameters.
These functions get us the position where we need to place the next
cursor, but the next issue is that currently NitroEngine and libDSF only
allow for specifying the position of the overall box to draw text in,
but not where to offset the cursor in that box. This leads to the
following problem ("Jonko" is in in green, the rest is in white):
"Dear god." Jonko quickly replied,
crestfallen.
Thus, the second change: I have introduced new versions of
`DSF_StringRender3D` and `DSF_StringRender3DAlpha` which accept an
`xStart` position that is added to the `pointerX`. Correspondingly, I
have added versions of `NE_RichTextRender3D` and
`NE_RichTextRender3DAlpha` that use these new functions as well.
Let me know if there are any issues with how I've written this (C is not
my forte, after all), but the first screenshot is this being tested in
production code.
This switches the rich text library from using a constant for the max
number of fonts to allowing the user to specify how many fonts should be
used dynamically. To accomplish this, a new method is introduced to the
rich text system: `NE_RichTextStartSystem`, which accepts the number of
slots to allocate. Those slots are allocated and then freed when
`NE_RichTextResetSystem` is called.
To maintain compatibility, I have not removed `NE_MAX_RICH_TEXT_FONTS`
(instead opting to mark it as deprecated), and have added functionality
to automatically call `NE_RichTextStartSystem` with that old value (8)
if the user decides to init a rich text slot before calling
`NE_RichTextStartSystem` (which would likely be the previous default
behavior).
This reverts commit 48ff37231f ("library: Remove unimplemented
functions from RichText system").
I thought the 3D drawing functions took a "depth" argument, but I was
wrong. I'm re-introducing the functions (and, this time, with an actual
implementation).
They were never meant to be there, they are a copy-paste error from taking
the code of the original text system to create the original files of the
rich text system.
This allows you to use one single texture for multiple images, and to
specify the coordinates that a sprite needs to use so that you don't need
to keep track of them.
In many cases the developer doesn't want to keep track of palettes and
materials as two different objects. This is only required if a palette
needs to be shared between multiple materials.
In cases where there a palette is only used by one material, this new
function can be used so that the developer doesn't need to keep track of
the palette object, only the material.
The functions called by NE_Process() and NE_ProcessDual() rely on global
data to know what to draw on the screen.
The functions called by the new NE_ProcessArg() and NE_ProcessDualArg()
can take arguments passed from the caller of the process function. This way
it isn't needed to have global variables.
Instead of having a different set of functions for each dual 3D mode,
have one set of functions that is clever enough to select the right code
to run based on the initialization function.
Now that there are 4 different possibilities (not initialized, single
screen 3D mode, dual 3D mode, safe dual 3D mode) it is better to have an
enum instead of multiple bools.
Regular dual 3D mode is very unstable. As soon as you drop one frame,
the last screen to be drawn will be displayed on both screens.
This new system is a lot more complex to setup, but it's reliable, and
it doesn't need a third main VRAM bank for triple buffering, just VRAM I
to act as pseudo framebuffer.
The old dual 3D mode has been kept for compatibility.
Documentation will come soon.
Thanks to @Gericom for the tips!
Now it is possible to select if the display lists are sent with a CPU
copy or with the DMA. This is needed for a future commit that will
introduce safe dual 3D mode.
The previous code relied too much on assertions, even for things that
are just regular things that may happen while the application is running
(like running out of memory).
- Many assertions have been turned into permanent checks
- Some functions now return "success" or "failure".
- NE_Init3D() and NE_InitDual3D() (and others) have been reworked to
handle failures gracefully.
Until now, a mesh was always owned by one single model. This made it
very difficult to clone models, because the mesh was always owned by one
of them. Whenever you deleted that model, the mesh was also deleted, and
all the cloned models would be unusable.
With the new system, there is a list of meshes with counters of how many
models use them. This way no matter how many models use the mesh, the
mesh will only be deleted when the last model is deleted.
In addition, in a similar way as before, a mesh has a flag that signals
if free() has to be called on the mesh data when the mesh is deleted.
This is useful when the mesh is loaded from a filesystem and stored in a
buffer allocated with malloc(), which is what Nitro Engine does.
Note that this code should be considered experimental for the time
being, until some tests are added.