From 0f572ba914c36af20c70666cadc99bb88fb8ede0 Mon Sep 17 00:00:00 2001 From: Damian Yerrick Date: Sat, 16 Sep 2023 19:01:04 -0400 Subject: [PATCH] convert equations in affobj to MathML with commented-out LaTeX in case we need to edit them later --- README.md | 5 + content/pages/affobj.md | 510 +++++++++++++++++++++++++++++++++------- 2 files changed, 426 insertions(+), 89 deletions(-) diff --git a/README.md b/README.md index 8af8c92..bdeb919 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,10 @@ For example: * Tables and Figures should be replaced with the raw HTML from the `-old.htm` file. +* Equations are coded in MathML, which displays in all browsers since 2023 according to [Can I use](https://caniuse.com/mathml). + It's recommended to write equations in LaTeX first, using a live preview tool such as [Online LaTeX Equation Editor](https://latexeditor.lagrida.com/). + Right-click the preview and choose Copy to Clipboard > MathML Code. + * Code blocks should have the correct language set on them (`c`, `asm`, `sh`, `makefile`) * Use backticks (`` ` ``) around code keywords and italics (`*`) around file names in the text. @@ -112,6 +116,7 @@ For autonumbering and cross-referencing of figures, tables and equations, we use * The figure/table/equation prefix is defined by the page title. + For example, on the page *'ii. Introduction to Tonc'*, the following Markdown: ```html diff --git a/content/pages/affobj.md b/content/pages/affobj.md index fc4d1bd..1ec0002 100644 --- a/content/pages/affobj.md +++ b/content/pages/affobj.md @@ -34,43 +34,159 @@ typedef struct OBJ_AFFINE ``` The *signed* 16-bit members `pa, pb, pc` and `pd` are 8.8 fixed point numbers that form the actual matrix, which I will refer to as **P**, in correspondence with the elements' names. For more information about this matrix, go to the [affine matrix](affine.html) section. Do so now if you haven't already, because I'm not going to repeat it here. If all you are after is a simple scale-then-rotate matrix, try this: for a zoom by s~x~ and s~y~ followed by a counter-clockwise rotation by α, the correct matrix is this: - - - -
  - P = - - - - - - -
  - pa -   - pb -   -
pc -   - pd -
-
= - - - - - - -
  - cos(α) / sx -   - −sin(α) / sx -   -
sin(α) / sy -   - cos(α) / sy -
-
+ + + + + + + + + P + = + + [ + + + + + p + + a + + + + + + p + + b + + + + + + + + p + + c + + + + + + p + + d + + + + + + ] + + = + + [ + + + + cos + + + ( + α + ) + + + / + + + s + + x + + + + + + sin + + + ( + α + ) + + + / + + + s + + x + + + + + + + + sin + + + ( + α + ) + + + / + + + s + + y + + + + + cos + + + ( + α + ) + + + / + + + s + + y + + + + + + ] + + + + + + + Note that the origin of the transformation is *center* of the sprite, not the top-left corner. This is worth remembering if you want to align your sprite with other objects, which we'll do later. @@ -90,12 +206,44 @@ Essential affine sprite steps The procedure that the GBA uses for drawing sprites is as follows: the sprite forms a rectangle on the screen defined by its size. To paint the screen pixels in that area (**q**) uses texture-pixel **p**, which is calculated via: - + +
-
(11.1) + ({!@eq:aff-ofs}) - p − p0 = - P · (q − q0), + + + + + + + p + + + p + + 0 + + + = + P + + ( + q + + + q + 0 + + ) + + + + + +
where **p**~0~ and **q**~0~ are the centers of the sprite in texture and screen space, respectively. The code below is essentially what the hardware does; it scans the screen-rectangle between plus and minus the half-width and half-height (half-sizes because the center is the reference point), calculates the texture-pixel and plots that color. @@ -443,21 +591,86 @@ To be frank though, calling `obj_aff_identity()` isn't necessary after a call to That's the set-up, now for how the demo does what it does. At any given time, you will have some transformation matrix, **P**. By pressing a button (or not), a small transformation of the current state will be performed, via matrix multiplication. +
  - Pnew = - Pold · D−1, + + + + + + + + P + + n + e + w + + + = + + P + + o + l + d + + + + + D + + + 1 + + + + + + + +
where **D** is either a small rotation (**R**), scaling (**S**) or shear (**H**). Or a no-op (**I**). However, there is a little hitch here. This would work nice in theory, but in *practice*, it won't work well because the fixed point matrix multiplications will result in unacceptable round-off errors very very quickly. Fortunately, all these transformations have the convenient property that - +
  - D(a)·D(b) = D(c). + + + + + + + D + ( + a + ) + + D + ( + b + ) + = + D + ( + c + ) + + + + + +
That is to say, multiple small transformations work as one big one. All you have to do is keep track of the current chosen transformation (the variable `aff_state`, in `get_aff_state()`), modify the state variable (`aff_value`), then calculate full transformation matrix (`get_aff_new()`) and apply that (with `obj_aff_postmul()`). When a different transformation type is chosen, the current matrix is saved, the state value is reset and the whole thing continues with that state until yet another is picked. The majority of the code is for keeping track of these changes; it's not pretty, but it gets the job done. @@ -506,60 +719,179 @@ For anchoring, you actually need one set of coordinates for each coordinate-spac -Yes, it is a whole lot of vectors, but funnily enough, most are already known. The center points (**c**~p~ and **c**~q~) can be derived from the objects size and double-size status, the anchors are known in advance because those are the input values, and **r**~p~ and **r**~q~ fit the general equation for the affine transformation, eq 1, so this links the two spaces. All that's left now is to write down and solve the set of equations. - - +Yes, it is a whole lot of vectors, but funnily enough, most are already known. The center points (**c**~p~ and **c**~q~) can be derived from the objects size and double-size status, the anchors are known in advance because those are the input values, and **r**~p~ and **r**~q~ fit the general equation for the affine transformation, {@eq:aff-ex-base}, so this links the two spaces. All that's left now is to write down and solve the set of equations. + +
- +
(11.2)({!@eq:aff-ex-base}) - - - - - - - - - - - - - - - - - - - -
x + cq + rq = q0
cp + rp = p0
rp = P·rq
+ + + + + + + + + + x + + + + c + + q + + + + + + r + + q + + + + + = + + + + q + + 0 + + + + + + + + c + + p + + + + + + r + + p + + + + + = + + + + p + + 0 + + + + + + + + r + + p + + + + + = + + + P + +   + + r + + q + + + + + + + + + + +
-Three equations with three unknowns, means it is solvable. I won't post the entire derivation because that's not all that difficult; what you see in eq 11.3 is the end result in the most usable form. - - +Three equations with three unknowns, means it is solvable. I won't post the entire derivation because that's not all that difficult; what you see in {@eq:aff-ex} is the end result in the most usable form. + +
-
(11.3) + ({!@eq:aff-ex}) - - - - - - - -
x - = - q0ms − - P−1· - (p0 − ½s) -
+ + + + + + + x + = + + q + + 0 + + + + m + s + + + P + + + 1 + + + + ( + + p + + 0 + + + + + + 1 + 2 + + + s + ) + + + + + +
-The right-hand side here has three separate vectors, two of which are part of the input, a scaling flag for the double-size mode, and the inverted affine matrix. Yes, I did say inverted. This is here because the translations to position the object correctly mostly take place in screen-space. The whole term using it is merely **r**~q~, the transformed difference between anchor and center in texture space, which you need for the final correction. +The right-hand side here has three separate vectors, two of which are part of the input, a scaling flag for the double-size mode, and the inverted affine matrix. Yes, I did say inverted. This is here because the translations to position the object correctly mostly take place in screen-space. The whole term using it is merely **r**q, the transformed difference between anchor and center in texture space, which you need for the final correction. -Now, this matrix inversion means two things. First, that you will likely have to set-up *two* matrices: the affine matrix itself, and its inverse. For general matrices, this might take a while, especially when considering that if you want scaling, you will have to do a division somewhere. Secondly, because you only have 16 bits for the matrix elements, the inverse won't be the *exact* inverse, meaning that aligning the objects exactly will be difficult, if not actually impossible. This is pretty much guaranteed by the hardware itself and I'll return to this point later on. For now, let's look at a function implementing eq 11.3 in the case of a 2-way scaling followed by a rotation. +Now, this matrix inversion means two things. First, that you will likely have to set up *two* matrices: the affine matrix itself, and its inverse. For general matrices, this might take a while, especially when considering that if you want scaling, you will have to do a division somewhere. Secondly, because you only have 16 bits for the matrix elements, the inverse won't be the *exact* inverse, meaning that aligning the objects exactly will be difficult, if not actually impossible. This is pretty much guaranteed by the hardware itself and I'll return to this point later on. For now, let's look at a function implementing {@eq:aff-ex} in the case of a 2-way scaling followed by a rotation.
```c @@ -628,7 +960,7 @@ void obj_rotscale_ex(OBJ_ATTR *obj, OBJ_AFFINE *oa, AFF_SRC_EX *asx) ```
-The `AFF_SRC_EX` struct and `oam_sizes` arrays are supporting entities of the function that does the positioning, which is `obj_rotscale_ex()`. This creates the affine matrix (`pa-pd`), and carries out all the necessary steps for eq 11.3, namely create the inverse matrix **A** (`aa-ad`), calculate all the offsets and correcting for the sizes, and finally updating the `OBJ_ATTR`. Note that the fixed point accuracy varies a lot, so it is important to comment often on this +The `AFF_SRC_EX` struct and `oam_sizes` arrays are supporting entities of the function that does the positioning, which is `obj_rotscale_ex()`. This creates the affine matrix (`pa-pd`), and carries out all the necessary steps for {@eq:aff-ex}, namely create the inverse matrix **A** (`aa-ad`), calculate all the offsets and correcting for the sizes, and finally updating the `OBJ_ATTR`. Note that the fixed point accuracy varies a lot, so it is important to comment often on this As I said, this is not a particularly fast function; it takes roughly a scanline worth of cycles. If you need more speed, I also have a Thumb assembly version which is about 40% faster.