sm64

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

envfx_bubbles.c (20073B)


      1 #include <ultra64.h>
      2 
      3 #include "sm64.h"
      4 #include "game_init.h"
      5 #include "memory.h"
      6 #include "envfx_snow.h"
      7 #include "envfx_bubbles.h"
      8 #include "engine/surface_collision.h"
      9 #include "engine/math_util.h"
     10 #include "engine/behavior_script.h"
     11 #include "audio/external.h"
     12 #include "textures.h"
     13 
     14 /**
     15  * This file implements environment effects that are not snow:
     16  * Flowers (unused), lava bubbles and jet stream/whirlpool bubbles.
     17  * Refer to 'envfx_snow.c' for more info about environment effects.
     18  * Note that the term 'bubbles' is used as a collective name for
     19  * effects in this file even though flowers aren't bubbles. For the
     20  * sake of concise naming, flowers fall under bubbles.
     21  */
     22 
     23 s16 gEnvFxBubbleConfig[10];
     24 static Gfx *sGfxCursor; // points to end of display list for bubble particles
     25 static s32 sBubbleParticleCount;
     26 static s32 sBubbleParticleMaxCount;
     27 
     28 UNUSED s32 D_80330690 = 0;
     29 UNUSED s32 D_80330694 = 0;
     30 
     31 /// Template for a bubble particle triangle
     32 Vtx_t gBubbleTempVtx[3] = {
     33     { { 0, 0, 0 }, 0, { 1544, 964 }, { 0xFF, 0xFF, 0xFF, 0xFF } },
     34     { { 0, 0, 0 }, 0, { 522, -568 }, { 0xFF, 0xFF, 0xFF, 0xFF } },
     35     { { 0, 0, 0 }, 0, { -498, 964 }, { 0xFF, 0xFF, 0xFF, 0xFF } },
     36 };
     37 
     38 /**
     39  * Check whether the particle with the given index is
     40  * laterally within distance of point (x, z). Used to
     41  * kill flower and bubble particles.
     42  */
     43 s32 particle_is_laterally_close(s32 index, s32 x, s32 z, s32 distance) {
     44     s32 xPos = (gEnvFxBuffer + index)->xPos;
     45     s32 zPos = (gEnvFxBuffer + index)->zPos;
     46 
     47     if (sqr(xPos - x) + sqr(zPos - z) > sqr(distance)) {
     48         return FALSE;
     49     }
     50 
     51     return TRUE;
     52 }
     53 
     54 /**
     55  * Generate a uniform random number in range [-2000, -1000[ or [1000, 2000[
     56  * Used to position flower particles
     57  */
     58 s32 random_flower_offset(void) {
     59     s32 result = random_float() * 2000.0f - 1000.0f;
     60     if (result < 0) {
     61         result -= 1000;
     62     } else {
     63         result += 1000;
     64     }
     65 
     66     return result;
     67 }
     68 
     69 /**
     70  * Update flower particles. Flowers are scattered randomly in front of the
     71  * camera, and can land on any ground
     72  */
     73 void envfx_update_flower(Vec3s centerPos) {
     74     s32 i;
     75     struct FloorGeometry *floorGeo; // unused
     76     s32 globalTimer = gGlobalTimer;
     77 
     78     s16 centerX = centerPos[0];
     79     UNUSED s16 centerY = centerPos[1];
     80     s16 centerZ = centerPos[2];
     81 
     82     for (i = 0; i < sBubbleParticleMaxCount; i++) {
     83         (gEnvFxBuffer + i)->isAlive = particle_is_laterally_close(i, centerX, centerZ, 3000);
     84         if (!(gEnvFxBuffer + i)->isAlive) {
     85             (gEnvFxBuffer + i)->xPos = random_flower_offset() + centerX;
     86             (gEnvFxBuffer + i)->zPos = random_flower_offset() + centerZ;
     87             (gEnvFxBuffer + i)->yPos = find_floor_height_and_data((gEnvFxBuffer + i)->xPos, 10000.0f,
     88                                                                   (gEnvFxBuffer + i)->zPos, &floorGeo);
     89             (gEnvFxBuffer + i)->isAlive = TRUE;
     90             (gEnvFxBuffer + i)->animFrame = random_float() * 5.0f;
     91         } else if (!(globalTimer & 3)) {
     92             (gEnvFxBuffer + i)->animFrame += 1;
     93             if ((gEnvFxBuffer + i)->animFrame > 5) {
     94                 (gEnvFxBuffer + i)->animFrame = 0;
     95             }
     96         }
     97     }
     98 }
     99 
    100 /**
    101  * Update the position of a lava bubble to be somewhere around centerPos
    102  * Uses find_floor to find the height of lava, if no floor or a non-lava
    103  * floor is found the bubble y is set to -10000, which is why you can see
    104  * occasional lava bubbles far below the course in Lethal Lava Land.
    105  * In the second Bowser fight arena, the visual lava is above the lava
    106  * floor so lava-bubbles are not normally visible, only if you bring the
    107  * camera below the lava plane.
    108  */
    109 void envfx_set_lava_bubble_position(s32 index, Vec3s centerPos) {
    110     struct Surface *surface;
    111     s16 floorY;
    112 
    113     s16 centerX = centerPos[0];
    114     s16 centerY = centerPos[1];
    115     s16 centerZ = centerPos[2];
    116 
    117     (gEnvFxBuffer + index)->xPos = random_float() * 6000.0f - 3000.0f + centerX;
    118     (gEnvFxBuffer + index)->zPos = random_float() * 6000.0f - 3000.0f + centerZ;
    119 
    120     if ((gEnvFxBuffer + index)->xPos > 8000) {
    121         (gEnvFxBuffer + index)->xPos = 16000 - (gEnvFxBuffer + index)->xPos;
    122     }
    123     if ((gEnvFxBuffer + index)->xPos < -8000) {
    124         (gEnvFxBuffer + index)->xPos = -16000 - (gEnvFxBuffer + index)->xPos;
    125     }
    126 
    127     if ((gEnvFxBuffer + index)->zPos > 8000) {
    128         (gEnvFxBuffer + index)->zPos = 16000 - (gEnvFxBuffer + index)->zPos;
    129     }
    130     if ((gEnvFxBuffer + index)->zPos < -8000) {
    131         (gEnvFxBuffer + index)->zPos = -16000 - (gEnvFxBuffer + index)->zPos;
    132     }
    133 
    134     floorY =
    135         find_floor((gEnvFxBuffer + index)->xPos, centerY + 500, (gEnvFxBuffer + index)->zPos, &surface);
    136     if (surface == NULL) {
    137         (gEnvFxBuffer + index)->yPos = FLOOR_LOWER_LIMIT_MISC;
    138         return;
    139     }
    140 
    141     if (surface->type == SURFACE_BURNING) {
    142         (gEnvFxBuffer + index)->yPos = floorY;
    143     } else {
    144         (gEnvFxBuffer + index)->yPos = FLOOR_LOWER_LIMIT_MISC;
    145     }
    146 }
    147 
    148 /**
    149  * Update lava bubble animation and give the bubble a new position if the
    150  * animation is over.
    151  */
    152 void envfx_update_lava(Vec3s centerPos) {
    153     s32 i;
    154     s32 globalTimer = gGlobalTimer;
    155     s8 chance;
    156 
    157     UNUSED s16 centerX = centerPos[0];
    158     UNUSED s16 centerY = centerPos[1];
    159     UNUSED s16 centerZ = centerPos[2];
    160 
    161     for (i = 0; i < sBubbleParticleMaxCount; i++) {
    162         if (!(gEnvFxBuffer + i)->isAlive) {
    163             envfx_set_lava_bubble_position(i, centerPos);
    164             (gEnvFxBuffer + i)->isAlive = TRUE;
    165         } else if (!(globalTimer & 1)) {
    166             (gEnvFxBuffer + i)->animFrame += 1;
    167             if ((gEnvFxBuffer + i)->animFrame > 8) {
    168                 (gEnvFxBuffer + i)->isAlive = FALSE;
    169                 (gEnvFxBuffer + i)->animFrame = 0;
    170             }
    171         }
    172     }
    173 
    174     if ((chance = (s32)(random_float() * 16.0f)) == 8) {
    175         play_sound(SOUND_GENERAL_QUIET_BUBBLE2, gGlobalSoundSource);
    176     }
    177 }
    178 
    179 /**
    180  * Rotate the input x, y and z around the rotation origin of the whirlpool
    181  * according to the pitch and yaw of the whirlpool.
    182  */
    183 void envfx_rotate_around_whirlpool(s32 *x, s32 *y, s32 *z) {
    184     s32 vecX = *x - gEnvFxBubbleConfig[ENVFX_STATE_DEST_X];
    185     s32 vecY = *y - gEnvFxBubbleConfig[ENVFX_STATE_DEST_Y];
    186     s32 vecZ = *z - gEnvFxBubbleConfig[ENVFX_STATE_DEST_Z];
    187     f32 cosPitch = coss(gEnvFxBubbleConfig[ENVFX_STATE_PITCH]);
    188     f32 sinPitch = sins(gEnvFxBubbleConfig[ENVFX_STATE_PITCH]);
    189     f32 cosMYaw = coss(-gEnvFxBubbleConfig[ENVFX_STATE_YAW]);
    190     f32 sinMYaw = sins(-gEnvFxBubbleConfig[ENVFX_STATE_YAW]);
    191 
    192     f32 rotatedX = vecX * cosMYaw - sinMYaw * cosPitch * vecY - sinPitch * sinMYaw * vecZ;
    193     f32 rotatedY = vecX * sinMYaw + cosPitch * cosMYaw * vecY - sinPitch * cosMYaw * vecZ;
    194     f32 rotatedZ = vecY * sinPitch + cosPitch * vecZ;
    195 
    196     *x = gEnvFxBubbleConfig[ENVFX_STATE_DEST_X] + (s32) rotatedX;
    197     *y = gEnvFxBubbleConfig[ENVFX_STATE_DEST_Y] + (s32) rotatedY;
    198     *z = gEnvFxBubbleConfig[ENVFX_STATE_DEST_Z] + (s32) rotatedZ;
    199 }
    200 
    201 /**
    202  * Check whether a whirlpool bubble is alive. A bubble respawns when it is too
    203  * low or close to the center.
    204  */
    205 s32 envfx_is_whirlpool_bubble_alive(s32 index) {
    206     UNUSED u8 filler[4];
    207 
    208     if ((gEnvFxBuffer + index)->bubbleY < gEnvFxBubbleConfig[ENVFX_STATE_DEST_Y] - 100) {
    209         return FALSE;
    210     }
    211 
    212     if ((gEnvFxBuffer + index)->angleAndDist[1] < 10) {
    213         return FALSE;
    214     }
    215 
    216     return TRUE;
    217 }
    218 
    219 /**
    220  * Update whirlpool particles. Whirlpool particles start high and far from
    221  * the center and get sucked into the sink in a spiraling motion.
    222  */
    223 void envfx_update_whirlpool(void) {
    224     s32 i;
    225 
    226     for (i = 0; i < sBubbleParticleMaxCount; i++) {
    227         (gEnvFxBuffer + i)->isAlive = envfx_is_whirlpool_bubble_alive(i);
    228         if (!(gEnvFxBuffer + i)->isAlive) {
    229             (gEnvFxBuffer + i)->angleAndDist[1] = random_float() * 1000.0f;
    230             (gEnvFxBuffer + i)->angleAndDist[0] = random_float() * 65536.0f;
    231             (gEnvFxBuffer + i)->xPos =
    232                 gEnvFxBubbleConfig[ENVFX_STATE_SRC_X]
    233                 + sins((gEnvFxBuffer + i)->angleAndDist[0]) * (gEnvFxBuffer + i)->angleAndDist[1];
    234             (gEnvFxBuffer + i)->zPos =
    235                 gEnvFxBubbleConfig[ENVFX_STATE_SRC_Z]
    236                 + coss((gEnvFxBuffer + i)->angleAndDist[0]) * (gEnvFxBuffer + i)->angleAndDist[1];
    237             (gEnvFxBuffer + i)->bubbleY =
    238                 gEnvFxBubbleConfig[ENVFX_STATE_SRC_Y] + (random_float() * 100.0f - 50.0f);
    239             (gEnvFxBuffer + i)->yPos = (i + gEnvFxBuffer)->bubbleY;
    240             (gEnvFxBuffer + i)->unusedBubbleVar = 0;
    241             (gEnvFxBuffer + i)->isAlive = TRUE;
    242 
    243             envfx_rotate_around_whirlpool(&(gEnvFxBuffer + i)->xPos, &(gEnvFxBuffer + i)->yPos,
    244                                           &(gEnvFxBuffer + i)->zPos);
    245         } else {
    246             (gEnvFxBuffer + i)->angleAndDist[1] -= 40;
    247             (gEnvFxBuffer + i)->angleAndDist[0] +=
    248                 (s16)(3000 - (gEnvFxBuffer + i)->angleAndDist[1] * 2) + 0x400;
    249             (gEnvFxBuffer + i)->xPos =
    250                 gEnvFxBubbleConfig[ENVFX_STATE_SRC_X]
    251                 + sins((gEnvFxBuffer + i)->angleAndDist[0]) * (gEnvFxBuffer + i)->angleAndDist[1];
    252             (gEnvFxBuffer + i)->zPos =
    253                 gEnvFxBubbleConfig[ENVFX_STATE_SRC_Z]
    254                 + coss((gEnvFxBuffer + i)->angleAndDist[0]) * (gEnvFxBuffer + i)->angleAndDist[1];
    255             (gEnvFxBuffer + i)->bubbleY -= 40 - ((s16)(gEnvFxBuffer + i)->angleAndDist[1] / 100);
    256             (gEnvFxBuffer + i)->yPos = (i + gEnvFxBuffer)->bubbleY;
    257 
    258             envfx_rotate_around_whirlpool(&(gEnvFxBuffer + i)->xPos, &(gEnvFxBuffer + i)->yPos,
    259                                           &(gEnvFxBuffer + i)->zPos);
    260         }
    261     }
    262 }
    263 
    264 /**
    265  * Check whether a jet stream bubble should respawn. Happens if it is laterally
    266  * 1000 units away from the source or 1500 units above it.
    267  */
    268 s32 envfx_is_jestream_bubble_alive(s32 index) {
    269     UNUSED u8 filler[4];
    270 
    271     if (!particle_is_laterally_close(index, gEnvFxBubbleConfig[ENVFX_STATE_SRC_X],
    272                                      gEnvFxBubbleConfig[ENVFX_STATE_SRC_Z], 1000)
    273         || gEnvFxBubbleConfig[ENVFX_STATE_SRC_Y] + 1500 < (gEnvFxBuffer + index)->yPos) {
    274         return FALSE;
    275     }
    276 
    277     return TRUE;
    278 }
    279 
    280 /**
    281  * Update the positions of jet stream bubble particles.
    282  * They move up and outwards.
    283  */
    284 void envfx_update_jetstream(void) {
    285     s32 i;
    286 
    287     for (i = 0; i < sBubbleParticleMaxCount; i++) {
    288         (gEnvFxBuffer + i)->isAlive = envfx_is_jestream_bubble_alive(i);
    289         if (!(gEnvFxBuffer + i)->isAlive) {
    290             (gEnvFxBuffer + i)->angleAndDist[1] = random_float() * 300.0f;
    291             (gEnvFxBuffer + i)->angleAndDist[0] = random_u16();
    292             (gEnvFxBuffer + i)->xPos =
    293                 gEnvFxBubbleConfig[ENVFX_STATE_SRC_X]
    294                 + sins((gEnvFxBuffer + i)->angleAndDist[0]) * (gEnvFxBuffer + i)->angleAndDist[1];
    295             (gEnvFxBuffer + i)->zPos =
    296                 gEnvFxBubbleConfig[ENVFX_STATE_SRC_Z]
    297                 + coss((gEnvFxBuffer + i)->angleAndDist[0]) * (gEnvFxBuffer + i)->angleAndDist[1];
    298             (gEnvFxBuffer + i)->yPos =
    299                 gEnvFxBubbleConfig[ENVFX_STATE_SRC_Y] + (random_float() * 400.0f - 200.0f);
    300         } else {
    301             (gEnvFxBuffer + i)->angleAndDist[1] += 10;
    302             (gEnvFxBuffer + i)->xPos += sins((gEnvFxBuffer + i)->angleAndDist[0]) * 10.0f;
    303             (gEnvFxBuffer + i)->zPos += coss((gEnvFxBuffer + i)->angleAndDist[0]) * 10.0f;
    304             (gEnvFxBuffer + i)->yPos -= ((gEnvFxBuffer + i)->angleAndDist[1] / 30) - 50;
    305         }
    306     }
    307 }
    308 
    309 /**
    310  * Initialize bubble (or flower) effect by allocating a buffer to store
    311  * the state of each particle and setting the initial and max count.
    312  * Analogous to init_snow_particles, but for bubbles.
    313  */
    314 s32 envfx_init_bubble(s32 mode) {
    315     s32 i;
    316 
    317     switch (mode) {
    318         case ENVFX_MODE_NONE:
    319             return FALSE;
    320 
    321         case ENVFX_FLOWERS:
    322             sBubbleParticleCount = 30;
    323             sBubbleParticleMaxCount = 30;
    324             break;
    325 
    326         case ENVFX_LAVA_BUBBLES:
    327             sBubbleParticleCount = 15;
    328             sBubbleParticleMaxCount = 15;
    329             break;
    330 
    331         case ENVFX_WHIRLPOOL_BUBBLES:
    332             sBubbleParticleCount = 60;
    333             break;
    334 
    335         case ENVFX_JETSTREAM_BUBBLES:
    336             sBubbleParticleCount = 60;
    337             break;
    338     }
    339 
    340     gEnvFxBuffer = mem_pool_alloc(gEffectsMemoryPool,
    341                                   sBubbleParticleCount * sizeof(struct EnvFxParticle));
    342     if (gEnvFxBuffer == NULL) {
    343         return FALSE;
    344     }
    345 
    346     bzero(gEnvFxBuffer, sBubbleParticleCount * sizeof(struct EnvFxParticle));
    347     bzero(gEnvFxBubbleConfig, sizeof(gEnvFxBubbleConfig));
    348 
    349     switch (mode) {
    350         case ENVFX_LAVA_BUBBLES:
    351             for (i = 0; i < sBubbleParticleCount; i++) {
    352                 (gEnvFxBuffer + i)->animFrame = random_float() * 7.0f;
    353             }
    354             break;
    355     }
    356 
    357     gEnvFxMode = mode;
    358     return TRUE;
    359 }
    360 
    361 /**
    362  * Update particles depending on mode.
    363  * Also sets the given vertices to the correct shape for each mode,
    364  * though they are not being rotated yet.
    365  */
    366 void envfx_bubbles_update_switch(s32 mode, Vec3s camTo, Vec3s vertex1, Vec3s vertex2, Vec3s vertex3) {
    367     switch (mode) {
    368         case ENVFX_FLOWERS:
    369             envfx_update_flower(camTo);
    370             vertex1[0] = 50;  vertex1[1] = 0;  vertex1[2] = 0;
    371             vertex2[0] = 0;   vertex2[1] = 75; vertex2[2] = 0;
    372             vertex3[0] = -50; vertex3[1] = 0;  vertex3[2] = 0;
    373             break;
    374 
    375         case ENVFX_LAVA_BUBBLES:
    376             envfx_update_lava(camTo);
    377             vertex1[0] = 100;  vertex1[1] = 0;   vertex1[2] = 0;
    378             vertex2[0] = 0;    vertex2[1] = 150; vertex2[2] = 0;
    379             vertex3[0] = -100; vertex3[1] = 0;   vertex3[2] = 0;
    380             break;
    381 
    382         case ENVFX_WHIRLPOOL_BUBBLES:
    383             envfx_update_whirlpool();
    384             vertex1[0] = 40;  vertex1[1] = 0;  vertex1[2] = 0;
    385             vertex2[0] = 0;   vertex2[1] = 60; vertex2[2] = 0;
    386             vertex3[0] = -40; vertex3[1] = 0;  vertex3[2] = 0;
    387             break;
    388 
    389         case ENVFX_JETSTREAM_BUBBLES:
    390             envfx_update_jetstream();
    391             vertex1[0] = 40;  vertex1[1] = 0;  vertex1[2] = 0;
    392             vertex2[0] = 0;   vertex2[1] = 60; vertex2[2] = 0;
    393             vertex3[0] = -40; vertex3[1] = 0;  vertex3[2] = 0;
    394             break;
    395     }
    396 }
    397 
    398 /**
    399  * Append 15 vertices to 'gfx', which is enough for 5 bubbles starting at
    400  * 'index'. The 3 input vertices represent the rotated triangle around (0,0,0)
    401  * that will be translated to bubble positions to draw the bubble image
    402  */
    403 void append_bubble_vertex_buffer(Gfx *gfx, s32 index, Vec3s vertex1, Vec3s vertex2, Vec3s vertex3,
    404                                  Vtx *template) {
    405     s32 i = 0;
    406     Vtx *vertBuf = alloc_display_list(15 * sizeof(Vtx));
    407 
    408     if (vertBuf == NULL) {
    409         return;
    410     }
    411 
    412     for (i = 0; i < 15; i += 3) {
    413         vertBuf[i] = template[0];
    414         (vertBuf + i)->v.ob[0] = (gEnvFxBuffer + (index + i / 3))->xPos + vertex1[0];
    415         (vertBuf + i)->v.ob[1] = (gEnvFxBuffer + (index + i / 3))->yPos + vertex1[1];
    416         (vertBuf + i)->v.ob[2] = (gEnvFxBuffer + (index + i / 3))->zPos + vertex1[2];
    417 
    418         vertBuf[i + 1] = template[1];
    419         (vertBuf + i + 1)->v.ob[0] = (gEnvFxBuffer + (index + i / 3))->xPos + vertex2[0];
    420         (vertBuf + i + 1)->v.ob[1] = (gEnvFxBuffer + (index + i / 3))->yPos + vertex2[1];
    421         (vertBuf + i + 1)->v.ob[2] = (gEnvFxBuffer + (index + i / 3))->zPos + vertex2[2];
    422 
    423         vertBuf[i + 2] = template[2];
    424         (vertBuf + i + 2)->v.ob[0] = (gEnvFxBuffer + (index + i / 3))->xPos + vertex3[0];
    425         (vertBuf + i + 2)->v.ob[1] = (gEnvFxBuffer + (index + i / 3))->yPos + vertex3[1];
    426         (vertBuf + i + 2)->v.ob[2] = (gEnvFxBuffer + (index + i / 3))->zPos + vertex3[2];
    427     }
    428 
    429     gSPVertex(gfx, VIRTUAL_TO_PHYSICAL(vertBuf), 15, 0);
    430 }
    431 
    432 /**
    433  * Appends to the enfvx display list a command setting the appropriate texture
    434  * for a specific particle. The display list is not passed as parameter but uses
    435  * the global sGfxCursor instead.
    436  */
    437 void envfx_set_bubble_texture(s32 mode, s16 index) {
    438     void **imageArr;
    439     s16 frame = (gEnvFxBuffer + index)->animFrame;
    440 
    441     switch (mode) {
    442         case ENVFX_FLOWERS:
    443             imageArr = segmented_to_virtual(&flower_bubbles_textures_ptr_0B002008);
    444             frame = (gEnvFxBuffer + index)->animFrame;
    445             break;
    446 
    447         case ENVFX_LAVA_BUBBLES:
    448             imageArr = segmented_to_virtual(&lava_bubble_ptr_0B006020);
    449             frame = (gEnvFxBuffer + index)->animFrame;
    450             break;
    451 
    452         case ENVFX_WHIRLPOOL_BUBBLES:
    453         case ENVFX_JETSTREAM_BUBBLES:
    454             imageArr = segmented_to_virtual(&bubble_ptr_0B006848);
    455             frame = 0;
    456             break;
    457     }
    458 
    459     gDPSetTextureImage(sGfxCursor++, G_IM_FMT_RGBA, G_IM_SIZ_16b, 1, *(imageArr + frame));
    460     gSPDisplayList(sGfxCursor++, &tiny_bubble_dl_0B006D68);
    461 }
    462 
    463 /**
    464  * Updates the bubble particle positions, then generates and returns a display
    465  * list drawing them.
    466  */
    467 Gfx *envfx_update_bubble_particles(s32 mode, UNUSED Vec3s marioPos, Vec3s camFrom, Vec3s camTo) {
    468     s32 i;
    469     s16 radius, pitch, yaw;
    470 
    471     Vec3s vertex1;
    472     Vec3s vertex2;
    473     Vec3s vertex3;
    474 
    475     Gfx *gfxStart = alloc_display_list(((sBubbleParticleMaxCount / 5) * 10 + sBubbleParticleMaxCount + 3)
    476                                        * sizeof(Gfx));
    477     if (gfxStart == NULL) {
    478         return NULL;
    479     }
    480 
    481     sGfxCursor = gfxStart;
    482 
    483     orbit_from_positions(camTo, camFrom, &radius, &pitch, &yaw);
    484     envfx_bubbles_update_switch(mode, camTo, vertex1, vertex2, vertex3);
    485     rotate_triangle_vertices(vertex1, vertex2, vertex3, pitch, yaw);
    486 
    487     gSPDisplayList(sGfxCursor++, &tiny_bubble_dl_0B006D38);
    488 
    489     for (i = 0; i < sBubbleParticleMaxCount; i += 5) {
    490         gDPPipeSync(sGfxCursor++);
    491         envfx_set_bubble_texture(mode, i);
    492         append_bubble_vertex_buffer(sGfxCursor++, i, vertex1, vertex2, vertex3, (Vtx *) gBubbleTempVtx);
    493         gSP1Triangle(sGfxCursor++, 0, 1, 2, 0);
    494         gSP1Triangle(sGfxCursor++, 3, 4, 5, 0);
    495         gSP1Triangle(sGfxCursor++, 6, 7, 8, 0);
    496         gSP1Triangle(sGfxCursor++, 9, 10, 11, 0);
    497         gSP1Triangle(sGfxCursor++, 12, 13, 14, 0);
    498     }
    499 
    500     gSPDisplayList(sGfxCursor++, &tiny_bubble_dl_0B006AB0);
    501     gSPEndDisplayList(sGfxCursor++);
    502 
    503     return gfxStart;
    504 }
    505 
    506 /**
    507  * Set the maximum particle count from the gEnvFxBubbleConfig variable,
    508  * which is set by the whirlpool or jet stream behavior.
    509  */
    510 void envfx_set_max_bubble_particles(s32 mode) {
    511     switch (mode) {
    512         case ENVFX_WHIRLPOOL_BUBBLES:
    513             sBubbleParticleMaxCount = gEnvFxBubbleConfig[ENVFX_STATE_PARTICLECOUNT];
    514             break;
    515         case ENVFX_JETSTREAM_BUBBLES:
    516             sBubbleParticleMaxCount = gEnvFxBubbleConfig[ENVFX_STATE_PARTICLECOUNT];
    517             break;
    518     }
    519 }
    520 
    521 /**
    522  * Update bubble-like environment effects. Assumes the mode is larger than 10,
    523  * lower modes are snow effects which are updated in a different function.
    524  * Returns a display list drawing the particles.
    525  */
    526 Gfx *envfx_update_bubbles(s32 mode, Vec3s marioPos, Vec3s camTo, Vec3s camFrom) {
    527     Gfx *gfx;
    528 
    529     if (gEnvFxMode == ENVFX_MODE_NONE && !envfx_init_bubble(mode)) {
    530         return NULL;
    531     }
    532 
    533     envfx_set_max_bubble_particles(mode);
    534 
    535     if (sBubbleParticleMaxCount == 0) {
    536         return NULL;
    537     }
    538 
    539     switch (mode) {
    540         case ENVFX_FLOWERS:
    541             gfx = envfx_update_bubble_particles(ENVFX_FLOWERS, marioPos, camFrom, camTo);
    542             break;
    543 
    544         case ENVFX_LAVA_BUBBLES:
    545             gfx = envfx_update_bubble_particles(ENVFX_LAVA_BUBBLES, marioPos, camFrom, camTo);
    546             break;
    547 
    548         case ENVFX_WHIRLPOOL_BUBBLES:
    549             gfx = envfx_update_bubble_particles(ENVFX_WHIRLPOOL_BUBBLES, marioPos, camFrom, camTo);
    550             break;
    551 
    552         case ENVFX_JETSTREAM_BUBBLES:
    553             gfx = envfx_update_bubble_particles(ENVFX_JETSTREAM_BUBBLES, marioPos, camFrom, camTo);
    554             break;
    555 
    556         default:
    557             return NULL;
    558     }
    559 
    560     return gfx;
    561 }