sm64

A Super Mario 64 decompilation
Log | Files | Refs | README | LICENSE

moving_texture.c (39729B)


      1 #include <ultra64.h>
      2 
      3 #include "sm64.h"
      4 #include "moving_texture.h"
      5 #include "area.h"
      6 #include "camera.h"
      7 #include "rendering_graph_node.h"
      8 #include "engine/math_util.h"
      9 #include "memory.h"
     10 #include "save_file.h"
     11 #include "segment2.h"
     12 #include "engine/surface_collision.h"
     13 #include "geo_misc.h"
     14 #include "rendering_graph_node.h"
     15 #include "object_list_processor.h"
     16 
     17 /**
     18  * This file contains functions for generating display lists with moving textures
     19  * (abbreviated movtex). This is used for water, sand, haze, mist and treadmills.
     20  * Each mesh using this system has the animated vertices stored as an array of shorts.
     21  * The first entry is the texture movement speed. After that the vertices are stored
     22  * in one of two layouts: one without per-vertex color attributes and one with.
     23  * [speed, v0(x,y,z, s,t)      , v1(x,y,z, s,t)      , ...]
     24  * [speed, v0(x,y,z, r,g,b s,t), v1(x,y,z, r,g,b s,t), ...]
     25  * x, y, z = vertex position as integers
     26  * s, t = texture coordinates as 6.10 fixed point number. That means coordinates in
     27  * range [0, 1024] are a unique part of the image, after that it repeats the image.
     28  *
     29  * The first vertex 'v0' is special because all subsequent vertices inherit its
     30  * texture offset. So to animate e.g. a treadmill, the speed component arr[0] is
     31  * simply added to the s component arr[7] every frame and the texture scrolls
     32  * horizontally over the entire mesh without changing the rest of the array.
     33  * Note that while the system allows many kinds of vertex animations, in
     34  * practice the only animation used is horizontally scrolling textures.
     35  *
     36  * After updating the base mesh, the vertices are converted to the format the RSP
     37  * understands and a display list is generated. The RSP can buffer 16 vertices at
     38  * a time, and this code assumes everything fits in one buffer, so every moving
     39  * texture mesh must have at most 16 vertices. As a result some meshes are split
     40  * up into multiple parts, like the sand pathway inside the pyramid which has 3
     41  * parts. The water stream in the Cavern of the Metal Cap fits in one mesh.
     42  *
     43  * Apart from this general system, there is also a simpler system for flat
     44  * quads with a rotating texture. This is often used for water, but also
     45  * for mist, toxic haze and lava inside the volcano. One quad is described
     46  * by the struct MovtexQuad, and multiple MovtexQuads form a MovtexQuadCollection.
     47  * A geo node has an id that corresponds to the id of a certain MovtexQuadCollection,
     48  * which will then be matched with the id of entries in gEnvironmentRegions to get the
     49  * y-position. The x and z coordinates are stored in the MovtexQuads themself,
     50  * so the water rectangle is separate from the actually drawn rectangle.
     51  */
     52 
     53 // First entry in array is texture movement speed for both layouts
     54 #define MOVTEX_ATTR_SPEED 0
     55 
     56 // Different layouts for vertices
     57 #define MOVTEX_LAYOUT_NOCOLOR 0
     58 #define MOVTEX_LAYOUT_COLORED 1
     59 
     60 // Attributes for movtex vertices
     61 #define MOVTEX_ATTR_X 1
     62 #define MOVTEX_ATTR_Y 2
     63 #define MOVTEX_ATTR_Z 3
     64 
     65 // For MOVTEX_LAYOUT_NOCOLOR only
     66 #define MOVTEX_ATTR_NOCOLOR_S 4
     67 #define MOVTEX_ATTR_NOCOLOR_T 5
     68 
     69 // For MOVTEX_LAYOUT_COLORED only
     70 #define MOVTEX_ATTR_COLORED_R 4
     71 #define MOVTEX_ATTR_COLORED_G 5
     72 #define MOVTEX_ATTR_COLORED_B 6
     73 #define MOVTEX_ATTR_COLORED_S 7
     74 #define MOVTEX_ATTR_COLORED_T 8
     75 
     76 /**
     77  * An object containing all info for a mesh with moving textures.
     78  * Contains the vertices that are animated, but also the display list which
     79  * determines the connectivity, as well as the texture, texture blend color
     80  * and drawing layer.
     81  */
     82 struct MovtexObject {
     83     /// number that geo nodes have as parameter to refer to this mesh
     84     u32 geoId;
     85     /// which texture to use for this mesh, index into gMovtexIdToTexture
     86     s32 textureId;
     87     /// amount of moving vertices
     88     s32 vtx_count;
     89     /// segmented address to movtex mesh with vertices
     90     void *movtexVerts;
     91     /// display list inserted before moving triangles
     92     Gfx *beginDl;
     93     /// display list inserted after moving triangles
     94     Gfx *endDl;
     95     /// display list with the actual moving texture triangles.
     96     /// Assumes the animated vertices are buffered and correct texture is set
     97     Gfx *triDl;
     98     // if the list does not have per-vertex colors, all vertices have these colors
     99     u8 r;      /// red
    100     u8 g;      /// green
    101     u8 b;      /// blue
    102     u8 a;      /// alpha
    103     s32 layer; /// the drawing layer for this mesh
    104 };
    105 
    106 /// Counters to make textures move iff the game is not paused.
    107 s16 gMovtexCounter = 1;
    108 s16 gMovtexCounterPrev = 0;
    109 
    110 // Vertex colors for rectangles. Used to give mist a tint
    111 #define MOVTEX_VTX_COLOR_DEFAULT 0 // no tint (white vertex colors)
    112 #define MOVTEX_VTX_COLOR_YELLOW 1  // used for Hazy Maze Cave toxic haze
    113 #define MOVTEX_VTX_COLOR_RED 2     // used for Shifting Sand Land around the Tox box maze
    114 
    115 s8 gMovtexVtxColor = MOVTEX_VTX_COLOR_DEFAULT;
    116 
    117 /// The height at which Mario entered the last painting. Used for Wet-Dry World only.
    118 float gPaintingMarioYEntry = 0.0f;
    119 
    120 /// Variable to ensure the initial Wet-Dry World water level is set only once
    121 s32 gWDWWaterLevelSet = FALSE;
    122 
    123 extern u8 ssl_quicksand[];
    124 extern u8 ssl_pyramid_sand[];
    125 extern u8 ttc_yellow_triangle[];
    126 
    127 /**
    128  * An array for converting a movtex texture id to a pointer that can
    129  * be passed to gDPSetTextureImage.
    130  */
    131 u8 *gMovtexIdToTexture[] = { texture_waterbox_water,     texture_waterbox_mist,
    132                              texture_waterbox_jrb_water, texture_waterbox_unknown_water,
    133                              texture_waterbox_lava,      ssl_quicksand,
    134                              ssl_pyramid_sand,           ttc_yellow_triangle };
    135 
    136 extern Gfx castle_grounds_dl_waterfall[];
    137 extern s16 castle_grounds_movtex_tris_waterfall[];
    138 extern s16 ssl_movtex_tris_pyramid_sand_pathway_front[];
    139 extern Gfx ssl_dl_pyramid_sand_pathway_begin[];
    140 extern Gfx ssl_dl_pyramid_sand_pathway_end[];
    141 extern Gfx ssl_dl_pyramid_sand_pathway_front_end[];
    142 extern s16 ssl_movtex_tris_pyramid_sand_pathway_floor[];
    143 extern Gfx ssl_dl_pyramid_sand_pathway_floor_begin[];
    144 extern Gfx ssl_dl_pyramid_sand_pathway_floor_end[];
    145 extern s16 ssl_movtex_tris_pyramid_sand_pathway_side[];
    146 extern Gfx ssl_dl_pyramid_sand_pathway_side_end[];
    147 extern s16 bitfs_movtex_tris_lava_first_section[];
    148 extern Gfx bitfs_dl_lava_sections[];
    149 extern s16 bitfs_movtex_tris_lava_second_section[];
    150 extern s16 bitfs_movtex_tris_lava_floor[];
    151 extern Gfx bitfs_dl_lava_floor[];
    152 extern s16 lll_movtex_tris_lava_floor[];
    153 extern Gfx lll_dl_lava_floor[];
    154 extern s16 lll_movtex_tris_lavafall_volcano[];
    155 extern Gfx lll_dl_lavafall_volcano[];
    156 extern s16 cotmc_movtex_tris_water[];
    157 extern Gfx cotmc_dl_water_begin[];
    158 extern Gfx cotmc_dl_water_end[];
    159 extern Gfx cotmc_dl_water[];
    160 extern s16 ttm_movtex_tris_begin_waterfall[];
    161 extern Gfx ttm_dl_waterfall[];
    162 extern s16 ttm_movtex_tris_end_waterfall[];
    163 extern s16 ttm_movtex_tris_begin_puddle_waterfall[];
    164 extern Gfx ttm_dl_bottom_waterfall[];
    165 extern s16 ttm_movtex_tris_end_puddle_waterfall[];
    166 extern s16 ttm_movtex_tris_puddle_waterfall[];
    167 extern Gfx ttm_dl_puddle_waterfall[];
    168 extern s16 ssl_movtex_tris_pyramid_quicksand[];
    169 extern Gfx ssl_dl_quicksand_begin[];
    170 extern Gfx ssl_dl_quicksand_end[];
    171 extern Gfx ssl_dl_pyramid_quicksand[];
    172 extern s16 ssl_movtex_tris_pyramid_corners_quicksand[];
    173 extern Gfx ssl_dl_pyramid_corners_quicksand[];
    174 extern s16 ssl_movtex_tris_sides_quicksand[];
    175 extern Gfx ssl_dl_sides_quicksand[];
    176 extern s16 ttc_movtex_tris_big_surface_treadmill[];
    177 extern Gfx ttc_dl_surface_treadmill_begin[];
    178 extern Gfx ttc_dl_surface_treadmill_end[];
    179 extern Gfx ttc_dl_surface_treadmill[];
    180 extern s16 ttc_movtex_tris_small_surface_treadmill[];
    181 extern s16 ssl_movtex_tris_quicksand_pit[];
    182 extern Gfx ssl_dl_quicksand_pit_begin[];
    183 extern Gfx ssl_dl_quicksand_pit_end[];
    184 extern Gfx ssl_dl_quicksand_pit[];
    185 extern s16 ssl_movtex_tris_pyramid_quicksand_pit[];
    186 extern Gfx ssl_dl_pyramid_quicksand_pit_begin[];
    187 extern Gfx ssl_dl_pyramid_quicksand_pit_end[];
    188 
    189 /**
    190  * MovtexObjects that have no color attributes per vertex (though the mesh
    191  * as a whole can have a blend color).
    192  */
    193 struct MovtexObject gMovtexNonColored[] = {
    194     // Inside the pyramid there is a sand pathway with the 5 secrets on it.
    195     // pathway_front is the highest 'sand fall', pathway_floor is the horizontal
    196     // sand stream and pathway_side is the lower 'sand fall'.
    197     { MOVTEX_PYRAMID_SAND_PATHWAY_FRONT, TEX_PYRAMID_SAND_SSL, 8,
    198       ssl_movtex_tris_pyramid_sand_pathway_front, ssl_dl_pyramid_sand_pathway_begin,
    199       ssl_dl_pyramid_sand_pathway_end, ssl_dl_pyramid_sand_pathway_front_end, 0xff, 0xff, 0xff, 0xff,
    200       LAYER_TRANSPARENT_INTER },
    201     { MOVTEX_PYRAMID_SAND_PATHWAY_FLOOR, TEX_PYRAMID_SAND_SSL, 8,
    202       ssl_movtex_tris_pyramid_sand_pathway_floor, ssl_dl_pyramid_sand_pathway_floor_begin,
    203       ssl_dl_pyramid_sand_pathway_floor_end, ssl_dl_pyramid_sand_pathway_front_end, 0xff, 0xff, 0xff,
    204       0xff, LAYER_OPAQUE_INTER },
    205     { MOVTEX_PYRAMID_SAND_PATHWAY_SIDE, TEX_PYRAMID_SAND_SSL, 6,
    206       ssl_movtex_tris_pyramid_sand_pathway_side, ssl_dl_pyramid_sand_pathway_begin,
    207       ssl_dl_pyramid_sand_pathway_end, ssl_dl_pyramid_sand_pathway_side_end, 0xff, 0xff, 0xff, 0xff,
    208       LAYER_TRANSPARENT_INTER },
    209 
    210     // The waterfall outside the castle
    211     { MOVTEX_CASTLE_WATERFALL, TEXTURE_WATER, 15, castle_grounds_movtex_tris_waterfall,
    212       dl_waterbox_rgba16_begin, dl_waterbox_end, castle_grounds_dl_waterfall, 0xff, 0xff, 0xff, 0xb4,
    213       LAYER_TRANSPARENT_INTER },
    214 
    215     // Bowser in the Fire Sea has lava at 3 heights, lava_floor is the lowest
    216     // and lava_second_section is the highest
    217     { MOVTEX_BITFS_LAVA_FIRST, TEXTURE_LAVA, 4, bitfs_movtex_tris_lava_first_section,
    218       dl_waterbox_rgba16_begin, dl_waterbox_end, bitfs_dl_lava_sections, 0xff, 0xff, 0xff, 0xff,
    219       LAYER_OPAQUE },
    220     { MOVTEX_BITFS_LAVA_SECOND, TEXTURE_LAVA, 4, bitfs_movtex_tris_lava_second_section,
    221       dl_waterbox_rgba16_begin, dl_waterbox_end, bitfs_dl_lava_sections, 0xff, 0xff, 0xff, 0xb4,
    222       LAYER_TRANSPARENT },
    223     { MOVTEX_BITFS_LAVA_FLOOR, TEXTURE_LAVA, 9, bitfs_movtex_tris_lava_floor, dl_waterbox_rgba16_begin,
    224       dl_waterbox_end, bitfs_dl_lava_floor, 0xff, 0xff, 0xff, 0xb4, LAYER_TRANSPARENT },
    225 
    226     // Lava floor in Lethal Lava Land and the lava fall in the volcano
    227     //! Note that the lava floor in the volcano is actually a quad.
    228     // The quad collection index LLL_MOVTEX_VOLCANO_FLOOR_LAVA is actually
    229     // 2 | MOVTEX_AREA_LLL, suggesting that the lava floor of LLL used to be a
    230     // quad too, with index 1.
    231     // It was probably too large however, resulting in overflowing texture
    232     // coordinates or other artifacts, so they converted it to a movtex
    233     // mesh with 9 vertices, subdividing the rectangle into 4 smaller ones.
    234     { MOVTEX_LLL_LAVA_FLOOR, TEXTURE_LAVA, 9, lll_movtex_tris_lava_floor, dl_waterbox_rgba16_begin,
    235       dl_waterbox_end, lll_dl_lava_floor, 0xff, 0xff, 0xff, 0xc8, LAYER_TRANSPARENT },
    236     { MOVTEX_VOLCANO_LAVA_FALL, TEXTURE_LAVA, 16, lll_movtex_tris_lavafall_volcano,
    237       dl_waterbox_rgba16_begin, dl_waterbox_end, lll_dl_lavafall_volcano, 0xff, 0xff, 0xff, 0xb4,
    238       LAYER_TRANSPARENT_INTER },
    239 
    240     // Cavern of the metal Cap has a waterfall source above the switch platform,
    241     // the stream, around the switch, and the waterfall that's the same as the one
    242     // outside the castle. They are all part of the same mesh.
    243     { MOVTEX_COTMC_WATER, TEXTURE_WATER, 14, cotmc_movtex_tris_water, cotmc_dl_water_begin,
    244       cotmc_dl_water_end, cotmc_dl_water, 0xff, 0xff, 0xff, 0xb4, LAYER_TRANSPARENT_INTER },
    245 
    246     // Tall, Tall mountain has water going from the top to the bottom of the mountain.
    247     { MOVTEX_TTM_BEGIN_WATERFALL, TEXTURE_WATER, 6, ttm_movtex_tris_begin_waterfall,
    248       dl_waterbox_rgba16_begin, dl_waterbox_end, ttm_dl_waterfall, 0xff, 0xff, 0xff, 0xb4,
    249       LAYER_TRANSPARENT },
    250     { MOVTEX_TTM_END_WATERFALL, TEXTURE_WATER, 6, ttm_movtex_tris_end_waterfall,
    251       dl_waterbox_rgba16_begin, dl_waterbox_end, ttm_dl_waterfall, 0xff, 0xff, 0xff, 0xb4,
    252       LAYER_TRANSPARENT },
    253     { MOVTEX_TTM_BEGIN_PUDDLE_WATERFALL, TEXTURE_WATER, 4, ttm_movtex_tris_begin_puddle_waterfall,
    254       dl_waterbox_rgba16_begin, dl_waterbox_end, ttm_dl_bottom_waterfall, 0xff, 0xff, 0xff, 0xb4,
    255       LAYER_TRANSPARENT_INTER },
    256     { MOVTEX_TTM_END_PUDDLE_WATERFALL, TEXTURE_WATER, 4, ttm_movtex_tris_end_puddle_waterfall,
    257       dl_waterbox_rgba16_begin, dl_waterbox_end, ttm_dl_bottom_waterfall, 0xff, 0xff, 0xff, 0xb4,
    258       LAYER_TRANSPARENT_INTER },
    259     { MOVTEX_TTM_PUDDLE_WATERFALL, TEXTURE_WATER, 8, ttm_movtex_tris_puddle_waterfall,
    260       dl_waterbox_rgba16_begin, dl_waterbox_end, ttm_dl_puddle_waterfall, 0xff, 0xff, 0xff, 0xb4,
    261       LAYER_TRANSPARENT_INTER },
    262     { 0x00000000, 0x00000000, 0, NULL, NULL, NULL, NULL, 0x00, 0x00, 0x00, 0x00, 0x00000000 },
    263 };
    264 
    265 /**
    266  * MovtexObjects that have color attributes per vertex.
    267  */
    268 struct MovtexObject gMovtexColored[] = {
    269     { MOVTEX_SSL_PYRAMID_SIDE, TEX_QUICKSAND_SSL, 12, ssl_movtex_tris_pyramid_quicksand,
    270       ssl_dl_quicksand_begin, ssl_dl_quicksand_end, ssl_dl_pyramid_quicksand, 0xff, 0xff, 0xff, 0xff,
    271       LAYER_OPAQUE },
    272     { MOVTEX_SSL_PYRAMID_CORNER, TEX_QUICKSAND_SSL, 16, ssl_movtex_tris_pyramid_corners_quicksand,
    273       ssl_dl_quicksand_begin, ssl_dl_quicksand_end, ssl_dl_pyramid_corners_quicksand, 0xff, 0xff, 0xff,
    274       0xff, LAYER_OPAQUE },
    275     { MOVTEX_SSL_COURSE_EDGE, TEX_QUICKSAND_SSL, 15, ssl_movtex_tris_sides_quicksand,
    276       ssl_dl_quicksand_begin, ssl_dl_quicksand_end, ssl_dl_sides_quicksand, 0xff, 0xff, 0xff, 0xff,
    277       LAYER_OPAQUE },
    278     { MOVTEX_TREADMILL_BIG, TEX_YELLOW_TRI_TTC, 12, ttc_movtex_tris_big_surface_treadmill,
    279       ttc_dl_surface_treadmill_begin, ttc_dl_surface_treadmill_end, ttc_dl_surface_treadmill, 0xff,
    280       0xff, 0xff, 0xff, LAYER_OPAQUE },
    281     { MOVTEX_TREADMILL_SMALL, TEX_YELLOW_TRI_TTC, 12, ttc_movtex_tris_small_surface_treadmill,
    282       ttc_dl_surface_treadmill_begin, ttc_dl_surface_treadmill_end, ttc_dl_surface_treadmill, 0xff,
    283       0xff, 0xff, 0xff, LAYER_OPAQUE },
    284     { 0x00000000, 0x00000000, 0, NULL, NULL, NULL, NULL, 0x00, 0x00, 0x00, 0x00, 0x00000000 },
    285 };
    286 
    287 /**
    288  * Treated identically to gMovtexColored.
    289  */
    290 struct MovtexObject gMovtexColored2[] = {
    291     { MOVTEX_SSL_SAND_PIT_OUTSIDE, TEX_QUICKSAND_SSL, 8, ssl_movtex_tris_quicksand_pit,
    292       ssl_dl_quicksand_pit_begin, ssl_dl_quicksand_pit_end, ssl_dl_quicksand_pit, 0xff, 0xff, 0xff,
    293       0xff, LAYER_OPAQUE },
    294     { MOVTEX_SSL_SAND_PIT_PYRAMID, TEX_PYRAMID_SAND_SSL, 8, ssl_movtex_tris_pyramid_quicksand_pit,
    295       ssl_dl_pyramid_quicksand_pit_begin, ssl_dl_pyramid_quicksand_pit_end, ssl_dl_quicksand_pit, 0xff,
    296       0xff, 0xff, 0xff, LAYER_OPAQUE },
    297     { 0x00000000, 0x00000000, 0, NULL, NULL, NULL, NULL, 0x00, 0x00, 0x00, 0x00, 0x00000000 },
    298 };
    299 
    300 /**
    301  * Sets the initial water level in Wet-Dry World based on how high Mario
    302  * jumped into the painting.
    303  */
    304 Gfx *geo_wdw_set_initial_water_level(s32 callContext, UNUSED struct GraphNode *node, UNUSED Mat4 mtx) {
    305     s32 i;
    306     UNUSED u8 unused[] = { 1, 0, 4, 0, 7, 0, 10, 0 };
    307     s16 wdwWaterHeight;
    308 
    309     // Why was this global variable needed when they could just check for GEO_CONTEXT_AREA_LOAD?
    310     if (callContext != GEO_CONTEXT_RENDER) {
    311         gWDWWaterLevelSet = FALSE;
    312     } else if (callContext == GEO_CONTEXT_RENDER && gEnvironmentRegions != NULL
    313                && !gWDWWaterLevelSet) {
    314         if (gPaintingMarioYEntry <= 1382.4) {
    315             wdwWaterHeight = 31;
    316         } else if (gPaintingMarioYEntry >= 1600.0) {
    317             wdwWaterHeight = 2816;
    318         } else {
    319             wdwWaterHeight = 1024;
    320         }
    321         for (i = 0; i < *gEnvironmentRegions; i++) {
    322             gEnvironmentRegions[i * 6 + 6] = wdwWaterHeight;
    323         }
    324         gWDWWaterLevelSet = TRUE;
    325     }
    326     return NULL;
    327 }
    328 
    329 /**
    330  * Update moving texture counters that determine when to update the coordinates.
    331  * Textures update when gMovtexCounterPrev != gMovtexCounter.
    332  * This ensures water / sand flow stops when the game pauses.
    333  */
    334 Gfx *geo_movtex_pause_control(s32 callContext, UNUSED struct GraphNode *node, UNUSED Mat4 mtx) {
    335     if (callContext != GEO_CONTEXT_RENDER) {
    336         gMovtexCounterPrev = gAreaUpdateCounter - 1;
    337         gMovtexCounter = gAreaUpdateCounter;
    338     } else {
    339         gMovtexCounterPrev = gMovtexCounter;
    340         gMovtexCounter = gAreaUpdateCounter;
    341     }
    342     return NULL;
    343 }
    344 
    345 /**
    346  * Make a vertex that's part of a quad with rotating texture.
    347  * verts: array of RSP vertices
    348  * n: index in 'verts' where the vertex is written
    349  * x, y, z: position
    350  * rot: base rotation of the texture
    351  * rotOffset: gets added to base rotation
    352  * scale: how often the texture repeats, 1 = no repeat
    353  */
    354 void movtex_make_quad_vertex(Vtx *verts, s32 index, s16 x, s16 y, s16 z, s16 rot, s16 rotOffset,
    355                              f32 scale, u8 alpha) {
    356     s16 s = 32.0 * (32.0 * scale - 1.0) * sins(rot + rotOffset);
    357     s16 t = 32.0 * (32.0 * scale - 1.0) * coss(rot + rotOffset);
    358 
    359     if (gMovtexVtxColor == MOVTEX_VTX_COLOR_YELLOW) {
    360         make_vertex(verts, index, x, y, z, s, t, 255, 255, 0, alpha);
    361     } else if (gMovtexVtxColor == MOVTEX_VTX_COLOR_RED) {
    362         make_vertex(verts, index, x, y, z, s, t, 255, 0, 0, alpha);
    363     } else {
    364         make_vertex(verts, index, x, y, z, s, t, 255, 255, 255, alpha);
    365     }
    366 }
    367 
    368 /**
    369  * Represents a single flat quad with a rotating texture
    370  * Stores x and z for 4 vertices, though it is often just a rectangle.
    371  * Does not store the y-position, since that can be dynamic for water levels.
    372  */
    373 struct MovtexQuad {
    374     /// the current texture rotation in this quad
    375     s16 rot;
    376     /// gets added to rot every frame
    377     s16 rotspeed;
    378     /// the amount of times the texture repeats. 1 = no repeat.
    379     s16 scale;
    380     /// Coordinates of vertices
    381     s16 x1;
    382     s16 z1;
    383     s16 x2;
    384     s16 z2;
    385     s16 x3;
    386     s16 z3;
    387     s16 x4;
    388     s16 z4;
    389     s16 rotDir;    /// if 1, it rotates counter-clockwise
    390     s16 alpha;     /// opacity, 255 = fully opaque
    391     s16 textureId; /// texture id
    392 };
    393 
    394 /// Variable for a little optimization: only set the texture when it differs from the previous texture
    395 s16 gMovetexLastTextureId;
    396 
    397 /**
    398  * Generates and returns a display list for a single MovtexQuad at height y.
    399  */
    400 Gfx *movtex_gen_from_quad(s16 y, struct MovtexQuad *quad) {
    401     s16 rot;
    402     s16 rotspeed = quad->rotspeed;
    403     s16 scale = quad->scale;
    404     s16 x1 = quad->x1;
    405     s16 z1 = quad->z1;
    406     s16 x2 = quad->x2;
    407     s16 z2 = quad->z2;
    408     s16 x3 = quad->x3;
    409     s16 z3 = quad->z3;
    410     s16 x4 = quad->x4;
    411     s16 z4 = quad->z4;
    412     s16 rotDir = quad->rotDir;
    413     s16 alpha = quad->alpha;
    414     s16 textureId = quad->textureId;
    415     Vtx *verts = alloc_display_list(4 * sizeof(*verts));
    416     Gfx *gfxHead;
    417     Gfx *gfx;
    418 
    419     if (textureId == gMovetexLastTextureId) {
    420         gfxHead = alloc_display_list(3 * sizeof(*gfxHead));
    421     } else {
    422         gfxHead = alloc_display_list(8 * sizeof(*gfxHead));
    423     }
    424 
    425     if (gfxHead == NULL || verts == NULL) {
    426         return NULL;
    427     }
    428     gfx = gfxHead;
    429     if (gMovtexCounter != gMovtexCounterPrev) {
    430         quad->rot += rotspeed;
    431     }
    432     rot = quad->rot;
    433     if (rotDir == ROTATE_CLOCKWISE) {
    434         movtex_make_quad_vertex(verts, 0, x1, y, z1, rot, 0, scale, alpha);
    435         movtex_make_quad_vertex(verts, 1, x2, y, z2, rot, 16384, scale, alpha);
    436         movtex_make_quad_vertex(verts, 2, x3, y, z3, rot, -32768, scale, alpha);
    437         movtex_make_quad_vertex(verts, 3, x4, y, z4, rot, -16384, scale, alpha);
    438     } else { // ROTATE_COUNTER_CLOCKWISE
    439         movtex_make_quad_vertex(verts, 0, x1, y, z1, rot, 0, scale, alpha);
    440         movtex_make_quad_vertex(verts, 1, x2, y, z2, rot, -16384, scale, alpha);
    441         movtex_make_quad_vertex(verts, 2, x3, y, z3, rot, -32768, scale, alpha);
    442         movtex_make_quad_vertex(verts, 3, x4, y, z4, rot, 16384, scale, alpha);
    443     }
    444 
    445     // Only add commands to change the texture when necessary
    446     if (textureId != gMovetexLastTextureId) {
    447         switch (textureId) {
    448             case TEXTURE_MIST: // an ia16 texture
    449                 gLoadBlockTexture(gfx++, 32, 32, G_IM_FMT_IA, gMovtexIdToTexture[textureId]);
    450                 break;
    451             default: // any rgba16 texture
    452                 gLoadBlockTexture(gfx++, 32, 32, G_IM_FMT_RGBA, gMovtexIdToTexture[textureId]);
    453                 break;
    454         }
    455         gMovetexLastTextureId = textureId;
    456     }
    457     gSPVertex(gfx++, VIRTUAL_TO_PHYSICAL2(verts), 4, 0);
    458     gSPDisplayList(gfx++, dl_draw_quad_verts_0123);
    459     gSPEndDisplayList(gfx);
    460     return gfxHead;
    461 }
    462 
    463 /**
    464  * Generate a display list drawing an array of MoxtexQuad at height 'y'.
    465  * y: y position of the quads
    466  * quadArrSegmented: a segmented address to an array of s16. The first number
    467  * is the number of entries, followed by that number of MovtexQuad structs.
    468  */
    469 Gfx *movtex_gen_from_quad_array(s16 y, void *quadArrSegmented) {
    470     s16 *quadArr = segmented_to_virtual(quadArrSegmented);
    471     s16 numLists = quadArr[0];
    472     Gfx *gfxHead = alloc_display_list((numLists + 1) * sizeof(*gfxHead));
    473     Gfx *gfx = gfxHead;
    474     Gfx *subList;
    475     s32 i;
    476 
    477     if (gfxHead == NULL) {
    478         return NULL;
    479     }
    480     for (i = 0; i < numLists; i++) {
    481         // quadArr is an array of s16, so sizeof(MovtexQuad) gets divided by 2
    482         subList = movtex_gen_from_quad(
    483             y, (struct MovtexQuad *) (&quadArr[i * (sizeof(struct MovtexQuad) / 2) + 1]));
    484         if (subList != NULL) {
    485             gSPDisplayList(gfx++, VIRTUAL_TO_PHYSICAL(subList));
    486         }
    487     }
    488     gSPEndDisplayList(gfx);
    489     return gfxHead;
    490 }
    491 
    492 /**
    493  * Generate the display list for a list of quads by searching through a collection
    494  * for a given id.
    495  * id: id of quad array to generate a list for
    496  * y: height at which the quads are drawn
    497  * movetexQuadsSegmented: segmented address to the MovtexQuadCollection array
    498  * that will be searched.
    499  */
    500 Gfx *movtex_gen_quads_id(s16 id, s16 y, void *movetexQuadsSegmented) {
    501     struct MovtexQuadCollection *collection = segmented_to_virtual(movetexQuadsSegmented);
    502     s32 i = 0;
    503 
    504     while (collection[i].id != -1) {
    505         if (collection[i].id == id) {
    506             return movtex_gen_from_quad_array(y, collection[i].quadArraySegmented);
    507         }
    508         i++;
    509     }
    510     return NULL;
    511 }
    512 
    513 extern u8 bbh_movtex_merry_go_round_water_entrance[];
    514 extern u8 bbh_movtex_merry_go_round_water_side[];
    515 extern u8 ccm_movtex_penguin_puddle_water[];
    516 extern u8 inside_castle_movtex_green_room_water[];
    517 extern u8 inside_castle_movtex_moat_water[];
    518 extern u8 hmc_movtex_dorrie_pool_water[];
    519 extern u8 hmc_movtex_toxic_maze_mist[];
    520 extern u8 ssl_movtex_puddle_water[];
    521 extern u8 ssl_movtex_toxbox_quicksand_mist[];
    522 extern u8 sl_movtex_water[];
    523 extern u8 wdw_movtex_area1_water[];
    524 extern u8 wdw_movtex_area2_water[];
    525 extern u8 jrb_movtex_water[];
    526 extern u8 jrb_movtex_initial_mist[];
    527 extern u8 jrb_movtex_sunken_ship_water[];
    528 extern u8 thi_movtex_area1_water[];
    529 extern u8 thi_movtex_area2_water[];
    530 extern u8 castle_grounds_movtex_water[];
    531 extern u8 lll_movtex_volcano_floor_lava[];
    532 extern u8 ddd_movtex_area1_water[];
    533 extern u8 ddd_movtex_area2_water[];
    534 extern u8 wf_movtex_water[];
    535 extern u8 castle_courtyard_movtex_star_statue_water[];
    536 extern u8 ttm_movtex_puddle[];
    537 
    538 /**
    539  * Find the quadCollection for a given quad collection id.
    540  */
    541 void *get_quad_collection_from_id(u32 id) {
    542     switch (id) {
    543         case BBH_MOVTEX_MERRY_GO_ROUND_WATER_ENTRANCE:
    544             return bbh_movtex_merry_go_round_water_entrance;
    545         case BBH_MOVTEX_MERRY_GO_ROUND_WATER_SIDE:
    546             return bbh_movtex_merry_go_round_water_side;
    547         case CCM_MOVTEX_PENGUIN_PUDDLE_WATER:
    548             return ccm_movtex_penguin_puddle_water;
    549         case INSIDE_CASTLE_MOVTEX_GREEN_ROOM_WATER:
    550             return inside_castle_movtex_green_room_water;
    551         case INSIDE_CASTLE_MOVTEX_MOAT_WATER:
    552             return inside_castle_movtex_moat_water;
    553         case HMC_MOVTEX_DORRIE_POOL_WATER:
    554             return hmc_movtex_dorrie_pool_water;
    555         case HMC_MOVTEX_TOXIC_MAZE_MIST:
    556             return hmc_movtex_toxic_maze_mist;
    557         case SSL_MOVTEX_PUDDLE_WATER:
    558             return ssl_movtex_puddle_water;
    559         case SSL_MOVTEX_TOXBOX_QUICKSAND_MIST:
    560             return ssl_movtex_toxbox_quicksand_mist;
    561         case SL_MOVTEX_WATER:
    562             return sl_movtex_water;
    563         case WDW_MOVTEX_AREA1_WATER:
    564             return wdw_movtex_area1_water;
    565         case WDW_MOVTEX_AREA2_WATER:
    566             return wdw_movtex_area2_water;
    567         case JRB_MOVTEX_WATER:
    568             return jrb_movtex_water;
    569         case JRB_MOVTEX_INITIAL_MIST:
    570             return jrb_movtex_initial_mist;
    571         case JRB_MOVTEX_SUNKEN_SHIP_WATER:
    572             return jrb_movtex_sunken_ship_water;
    573         case THI_MOVTEX_AREA1_WATER:
    574             return thi_movtex_area1_water;
    575         case THI_MOVTEX_AREA2_WATER:
    576             return thi_movtex_area2_water;
    577         case CASTLE_GROUNDS_MOVTEX_WATER:
    578             return castle_grounds_movtex_water;
    579         case LLL_MOVTEX_VOLCANO_FLOOR_LAVA:
    580             return lll_movtex_volcano_floor_lava;
    581         case DDD_MOVTEX_AREA1_WATER:
    582             return ddd_movtex_area1_water;
    583         case DDD_MOVTEX_AREA2_WATER:
    584             return ddd_movtex_area2_water;
    585         case WF_MOVTEX_WATER:
    586             return wf_movtex_water;
    587         case CASTLE_COURTYARD_MOVTEX_STAR_STATUE_WATER:
    588             return castle_courtyard_movtex_star_statue_water;
    589         case TTM_MOVTEX_PUDDLE:
    590             return ttm_movtex_puddle;
    591         default:
    592             return NULL;
    593     }
    594 }
    595 
    596 /**
    597  * Write to 'gfx' a command to set the current texture format for the given
    598  * quadCollection.
    599  */
    600 void movtex_change_texture_format(u32 quadCollectionId, Gfx **gfx) {
    601     switch (quadCollectionId) {
    602         case HMC_MOVTEX_TOXIC_MAZE_MIST:
    603             gSPDisplayList((*gfx)++, dl_waterbox_ia16_begin);
    604             break;
    605         case SSL_MOVTEX_TOXBOX_QUICKSAND_MIST:
    606             gSPDisplayList((*gfx)++, dl_waterbox_ia16_begin);
    607             break;
    608         case JRB_MOVTEX_INITIAL_MIST:
    609             gSPDisplayList((*gfx)++, dl_waterbox_ia16_begin);
    610             break;
    611         default:
    612             gSPDisplayList((*gfx)++, dl_waterbox_rgba16_begin);
    613             break;
    614     }
    615 }
    616 
    617 /**
    618  * Geo script responsible for drawing quads with a moving texture at the height
    619  * of the corresponding water region. The node's parameter determines which quad
    620  * collection is drawn, see moving_texture.h.
    621  */
    622 Gfx *geo_movtex_draw_water_regions(s32 callContext, struct GraphNode *node, UNUSED Mat4 mtx) {
    623     Gfx *gfxHead = NULL;
    624     Gfx *gfx = NULL;
    625     Gfx *subList;
    626     void *quadCollection;
    627     struct GraphNodeGenerated *asGenerated;
    628     s16 numWaterBoxes;
    629     s16 waterId;
    630     s16 waterY;
    631     s32 i;
    632 
    633     if (callContext == GEO_CONTEXT_RENDER) {
    634         gMovtexVtxColor = MOVTEX_VTX_COLOR_DEFAULT;
    635         if (gEnvironmentRegions == NULL) {
    636             return NULL;
    637         }
    638         numWaterBoxes = gEnvironmentRegions[0];
    639         gfxHead = alloc_display_list((numWaterBoxes + 3) * sizeof(*gfxHead));
    640         if (gfxHead == NULL) {
    641             return NULL;
    642         } else {
    643             gfx = gfxHead;
    644         }
    645         asGenerated = (struct GraphNodeGenerated *) node;
    646         if (asGenerated->parameter == JRB_MOVTEX_INITIAL_MIST) {
    647             if (gLakituState.goalPos[1] < 1024.0) { // if camera under water
    648                 return NULL;
    649             }
    650             if (save_file_get_star_flags(gCurrSaveFileNum - 1, COURSE_NUM_TO_INDEX(COURSE_JRB))
    651                 & (1 << 0)) { // the "Plunder in the Sunken Ship" star in JRB is collected
    652                 return NULL;
    653             }
    654         } else if (asGenerated->parameter == HMC_MOVTEX_TOXIC_MAZE_MIST) {
    655             gMovtexVtxColor = MOVTEX_VTX_COLOR_YELLOW;
    656         } else if (asGenerated->parameter == SSL_MOVTEX_TOXBOX_QUICKSAND_MIST) {
    657             gMovtexVtxColor = MOVTEX_VTX_COLOR_RED;
    658         }
    659         quadCollection = get_quad_collection_from_id(asGenerated->parameter);
    660         if (quadCollection == NULL) {
    661             return NULL;
    662         }
    663 
    664         asGenerated->fnNode.node.flags =
    665             (asGenerated->fnNode.node.flags & 0xFF) | (LAYER_TRANSPARENT_INTER << 8);
    666 
    667         movtex_change_texture_format(asGenerated->parameter, &gfx);
    668         gMovetexLastTextureId = -1;
    669         for (i = 0; i < numWaterBoxes; i++) {
    670             waterId = gEnvironmentRegions[i * 6 + 1];
    671             waterY = gEnvironmentRegions[i * 6 + 6];
    672             subList = movtex_gen_quads_id(waterId, waterY, quadCollection);
    673             if (subList != NULL) {
    674                 gSPDisplayList(gfx++, VIRTUAL_TO_PHYSICAL(subList));
    675             }
    676         }
    677         gSPDisplayList(gfx++, dl_waterbox_end);
    678         gSPEndDisplayList(gfx);
    679     }
    680     return gfxHead;
    681 }
    682 
    683 /**
    684  * Updates a movtex mesh by adding the movtex's speed to the horizontal or
    685  * vertical texture coordinates depending on 'attr'.
    686  * movtexVerts: vertices to update
    687  * attr: which attribute to change
    688  */
    689 void update_moving_texture_offset(s16 *movtexVerts, s32 attr) {
    690     s16 movSpeed = movtexVerts[MOVTEX_ATTR_SPEED];
    691     s16 *curOffset = movtexVerts + attr;
    692 
    693     if (gMovtexCounter != gMovtexCounterPrev) {
    694         *curOffset += movSpeed;
    695         // note that texture coordinates are 6.10 fixed point, so this does modulo 1
    696         if (*curOffset >= 1024) {
    697             *curOffset -= 1024;
    698         }
    699         if (*curOffset <= -1024) {
    700             *curOffset += 1024;
    701         }
    702     }
    703 }
    704 
    705 /**
    706  * Make the first vertex of a moving texture with index 0.
    707  * This vertex is the base of all vertices with index > 0, which use this
    708  * vertex's coordinates as base on which to apply offset.
    709  * The first vertex has offset 0 by definition, simplifying the calculations a bit.
    710  */
    711 void movtex_write_vertex_first(Vtx *vtx, s16 *movtexVerts, struct MovtexObject *c, s8 attrLayout) {
    712     s16 x = movtexVerts[MOVTEX_ATTR_X];
    713     s16 y = movtexVerts[MOVTEX_ATTR_Y];
    714     s16 z = movtexVerts[MOVTEX_ATTR_Z];
    715     u8 alpha = c->a;
    716     u8 r1;
    717     u8 g1;
    718     u8 b1;
    719     s8 r2;
    720     s8 g2;
    721     s8 b2;
    722     s16 s;
    723     s16 t;
    724 
    725     switch (attrLayout) {
    726         case MOVTEX_LAYOUT_NOCOLOR:
    727             r1 = c->r;
    728             g1 = c->g;
    729             b1 = c->b;
    730             s = movtexVerts[MOVTEX_ATTR_NOCOLOR_S];
    731             t = movtexVerts[MOVTEX_ATTR_NOCOLOR_T];
    732             make_vertex(vtx, 0, x, y, z, s, t, r1, g1, b1, alpha);
    733             break;
    734         case MOVTEX_LAYOUT_COLORED:
    735             r2 = movtexVerts[MOVTEX_ATTR_COLORED_R];
    736             g2 = movtexVerts[MOVTEX_ATTR_COLORED_G];
    737             b2 = movtexVerts[MOVTEX_ATTR_COLORED_B];
    738             s = movtexVerts[MOVTEX_ATTR_COLORED_S];
    739             t = movtexVerts[MOVTEX_ATTR_COLORED_T];
    740             make_vertex(vtx, 0, x, y, z, s, t, r2, g2, b2, alpha);
    741             break;
    742     }
    743 }
    744 
    745 /**
    746  * Make a vertex with index > 0. The vertex with index 0 is made in
    747  * movtex_write_vertex_first and subsequent vertices use vertex 0 as a base
    748  * for their texture coordinates.
    749  */
    750 void movtex_write_vertex_index(Vtx *verts, s32 index, s16 *movtexVerts, struct MovtexObject *d,
    751                                s8 attrLayout) {
    752     u8 alpha = d->a;
    753     s16 x;
    754     s16 y;
    755     s16 z;
    756     s16 baseS;
    757     s16 baseT;
    758     s16 s;
    759     s16 t;
    760     s16 offS;
    761     s16 offT;
    762     u8 r1;
    763     u8 g1;
    764     u8 b1;
    765     s8 r2;
    766     s8 g2;
    767     s8 b2;
    768 
    769     switch (attrLayout) {
    770         case MOVTEX_LAYOUT_NOCOLOR:
    771             x = movtexVerts[index * 5 + MOVTEX_ATTR_X];
    772             y = movtexVerts[index * 5 + MOVTEX_ATTR_Y];
    773             z = movtexVerts[index * 5 + MOVTEX_ATTR_Z];
    774             baseS = movtexVerts[MOVTEX_ATTR_NOCOLOR_S];
    775             baseT = movtexVerts[MOVTEX_ATTR_NOCOLOR_T];
    776             offS = movtexVerts[index * 5 + MOVTEX_ATTR_NOCOLOR_S];
    777             offT = movtexVerts[index * 5 + MOVTEX_ATTR_NOCOLOR_T];
    778             s = baseS + ((offS * 32) * 32U);
    779             t = baseT + ((offT * 32) * 32U);
    780             r1 = d->r;
    781             g1 = d->g;
    782             b1 = d->b;
    783             make_vertex(verts, index, x, y, z, s, t, r1, g1, b1, alpha);
    784             break;
    785         case MOVTEX_LAYOUT_COLORED:
    786             x = movtexVerts[index * 8 + MOVTEX_ATTR_X];
    787             y = movtexVerts[index * 8 + MOVTEX_ATTR_Y];
    788             z = movtexVerts[index * 8 + MOVTEX_ATTR_Z];
    789             baseS = movtexVerts[7];
    790             baseT = movtexVerts[8];
    791             offS = movtexVerts[index * 8 + 7];
    792             offT = movtexVerts[index * 8 + 8];
    793             s = baseS + ((offS * 32) * 32U);
    794             t = baseT + ((offT * 32) * 32U);
    795             r2 = movtexVerts[index * 8 + MOVTEX_ATTR_COLORED_R];
    796             g2 = movtexVerts[index * 8 + MOVTEX_ATTR_COLORED_G];
    797             b2 = movtexVerts[index * 8 + MOVTEX_ATTR_COLORED_B];
    798             make_vertex(verts, index, x, y, z, s, t, r2, g2, b2, alpha);
    799             break;
    800     }
    801 }
    802 
    803 /**
    804  * Generate a displaylist for a MovtexObject.
    805  * 'attrLayout' is one of MOVTEX_LAYOUT_NOCOLOR and MOVTEX_LAYOUT_COLORED.
    806  */
    807 Gfx *movtex_gen_list(s16 *movtexVerts, struct MovtexObject *movtexList, s8 attrLayout) {
    808     Vtx *verts = alloc_display_list(movtexList->vtx_count * sizeof(*verts));
    809     Gfx *gfxHead = alloc_display_list(11 * sizeof(*gfxHead));
    810     Gfx *gfx = gfxHead;
    811     s32 i;
    812 
    813     if (verts == NULL || gfxHead == NULL) {
    814         return NULL;
    815     }
    816 
    817     movtex_write_vertex_first(verts, movtexVerts, movtexList, attrLayout);
    818     for (i = 1; i < movtexList->vtx_count; i++) {
    819         movtex_write_vertex_index(verts, i, movtexVerts, movtexList, attrLayout);
    820     }
    821 
    822     gSPDisplayList(gfx++, movtexList->beginDl);
    823     gLoadBlockTexture(gfx++, 32, 32, G_IM_FMT_RGBA, gMovtexIdToTexture[movtexList->textureId]);
    824     gSPVertex(gfx++, VIRTUAL_TO_PHYSICAL2(verts), movtexList->vtx_count, 0);
    825     gSPDisplayList(gfx++, movtexList->triDl);
    826     gSPDisplayList(gfx++, movtexList->endDl);
    827     gSPEndDisplayList(gfx);
    828     return gfxHead;
    829 }
    830 
    831 /**
    832  * Function for a geo node that draws a MovtexObject in the gMovtexNonColored list.
    833  */
    834 Gfx *geo_movtex_draw_nocolor(s32 callContext, struct GraphNode *node, UNUSED Mat4 mtx) {
    835     s32 i;
    836     s16 *movtexVerts;
    837     struct GraphNodeGenerated *asGenerated;
    838     Gfx *gfx = NULL;
    839 
    840     if (callContext == GEO_CONTEXT_RENDER) {
    841         i = 0;
    842         asGenerated = (struct GraphNodeGenerated *) node;
    843         while (gMovtexNonColored[i].movtexVerts != 0) {
    844             if (gMovtexNonColored[i].geoId == asGenerated->parameter) {
    845                 asGenerated->fnNode.node.flags =
    846                     (asGenerated->fnNode.node.flags & 0xFF) | (gMovtexNonColored[i].layer << 8);
    847                 movtexVerts = segmented_to_virtual(gMovtexNonColored[i].movtexVerts);
    848                 update_moving_texture_offset(movtexVerts, MOVTEX_ATTR_NOCOLOR_S);
    849                 gfx = movtex_gen_list(movtexVerts, &gMovtexNonColored[i],
    850                                       MOVTEX_LAYOUT_NOCOLOR); // no perVertex colors
    851                 break;
    852             }
    853             i++;
    854         }
    855     }
    856     return gfx;
    857 }
    858 
    859 /**
    860  * Function for a geo node that draws a MovtexObject in the gMovtexColored list.
    861  */
    862 Gfx *geo_movtex_draw_colored(s32 callContext, struct GraphNode *node, UNUSED Mat4 mtx) {
    863     s32 i;
    864     s16 *movtexVerts;
    865     struct GraphNodeGenerated *asGenerated;
    866     Gfx *gfx = NULL;
    867 
    868     if (callContext == GEO_CONTEXT_RENDER) {
    869         i = 0;
    870         asGenerated = (struct GraphNodeGenerated *) node;
    871         while (gMovtexColored[i].movtexVerts != 0) {
    872             if (gMovtexColored[i].geoId == asGenerated->parameter) {
    873                 asGenerated->fnNode.node.flags =
    874                     (asGenerated->fnNode.node.flags & 0xFF) | (gMovtexColored[i].layer << 8);
    875                 movtexVerts = segmented_to_virtual(gMovtexColored[i].movtexVerts);
    876                 update_moving_texture_offset(movtexVerts, MOVTEX_ATTR_COLORED_S);
    877                 gfx = movtex_gen_list(movtexVerts, &gMovtexColored[i], MOVTEX_LAYOUT_COLORED);
    878                 break;
    879             }
    880             i++;
    881         }
    882     }
    883     return gfx;
    884 }
    885 
    886 /**
    887  * Function for a geo node that draws a MovtexObject in the gMovtexColored list,
    888  * but it doesn't call update_moving_texture_offset since that happens in
    889  * geo_movtex_update_horizontal. This is for when a MovtexObject has multiple
    890  * instances (like TTC treadmills) so you don't want the animation speed to
    891  * increase the more instances there are.
    892  */
    893 Gfx *geo_movtex_draw_colored_no_update(s32 callContext, struct GraphNode *node, UNUSED Mat4 mtx) {
    894     s32 i;
    895     s16 *movtexVerts;
    896     struct GraphNodeGenerated *asGenerated;
    897     Gfx *gfx = NULL;
    898 
    899     if (callContext == GEO_CONTEXT_RENDER) {
    900         i = 0;
    901         asGenerated = (struct GraphNodeGenerated *) node;
    902         while (gMovtexColored[i].movtexVerts != 0) {
    903             if (gMovtexColored[i].geoId == asGenerated->parameter) {
    904                 asGenerated->fnNode.node.flags =
    905                     (asGenerated->fnNode.node.flags & 0xFF) | (gMovtexColored[i].layer << 8);
    906                 movtexVerts = segmented_to_virtual(gMovtexColored[i].movtexVerts);
    907                 gfx = movtex_gen_list(movtexVerts, &gMovtexColored[i], MOVTEX_LAYOUT_COLORED);
    908                 break;
    909             }
    910             i++;
    911         }
    912     }
    913     return gfx;
    914 }
    915 
    916 /**
    917  * Exact copy of geo_movtex_draw_colored_no_update, but now using the gMovtexColored2 array.
    918  * Used for the sand pits in SSL, both outside and inside the pyramid.
    919  */
    920 Gfx *geo_movtex_draw_colored_2_no_update(s32 callContext, struct GraphNode *node, UNUSED Mat4 mtx) {
    921     s32 i;
    922     s16 *movtexVerts;
    923     struct GraphNodeGenerated *asGenerated;
    924     Gfx *gfx = NULL;
    925 
    926     if (callContext == GEO_CONTEXT_RENDER) {
    927         i = 0;
    928         asGenerated = (struct GraphNodeGenerated *) node;
    929         while (gMovtexColored2[i].movtexVerts != 0) {
    930             if (gMovtexColored2[i].geoId == asGenerated->parameter) {
    931                 asGenerated->fnNode.node.flags =
    932                     (asGenerated->fnNode.node.flags & 0xFF) | (gMovtexColored2[i].layer << 8);
    933                 movtexVerts = segmented_to_virtual(gMovtexColored2[i].movtexVerts);
    934                 gfx = movtex_gen_list(movtexVerts, &gMovtexColored2[i], MOVTEX_LAYOUT_COLORED);
    935                 break;
    936             }
    937             i++;
    938         }
    939     }
    940     return gfx;
    941 }
    942 
    943 /**
    944  * Make textures move horizontally by simply adding a number to the 's' texture coordinate.
    945  * Used for:
    946  * - treadmills in Tick Tock Clock
    947  * - sand pits outside and inside the pyramid in Shifting Sand
    948  * Note that the drawing for these happen in different nodes with functions
    949  * geo_movtex_draw_colored_no_update and geo_movtex_draw_colored_2_no_update.
    950  * Usually the updating happens in the same function that draws it, but in
    951  * these cases the same model has multiple instances, and you don't want the
    952  * model to update multiple times.
    953  * Note that the final TTC only has one big treadmill though.
    954  */
    955 Gfx *geo_movtex_update_horizontal(s32 callContext, struct GraphNode *node, UNUSED Mat4 mtx) {
    956     void *movtexVerts;
    957 
    958     if (callContext == GEO_CONTEXT_RENDER) {
    959         struct GraphNodeGenerated *asGenerated = (struct GraphNodeGenerated *) node;
    960 
    961         switch (asGenerated->parameter) {
    962             case MOVTEX_SSL_SAND_PIT_OUTSIDE:
    963                 movtexVerts = segmented_to_virtual(ssl_movtex_tris_quicksand_pit);
    964                 break;
    965             case MOVTEX_SSL_SAND_PIT_PYRAMID:
    966                 movtexVerts = segmented_to_virtual(ssl_movtex_tris_pyramid_quicksand_pit);
    967                 break;
    968             case MOVTEX_TREADMILL_BIG:
    969                 movtexVerts = segmented_to_virtual(ttc_movtex_tris_big_surface_treadmill);
    970                 break;
    971             case MOVTEX_TREADMILL_SMALL:
    972                 movtexVerts = segmented_to_virtual(ttc_movtex_tris_small_surface_treadmill);
    973                 break;
    974         }
    975         update_moving_texture_offset(movtexVerts, MOVTEX_ATTR_COLORED_S);
    976     }
    977     return NULL;
    978 }