sm64

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

envfx_snow.c (17977B)


      1 #include <ultra64.h>
      2 
      3 #include "sm64.h"
      4 #include "dialog_ids.h"
      5 #include "game_init.h"
      6 #include "memory.h"
      7 #include "ingame_menu.h"
      8 #include "envfx_snow.h"
      9 #include "envfx_bubbles.h"
     10 #include "engine/surface_collision.h"
     11 #include "engine/math_util.h"
     12 #include "engine/behavior_script.h"
     13 #include "audio/external.h"
     14 #include "obj_behaviors.h"
     15 
     16 /**
     17  * This file contains the function that handles 'environment effects',
     18  * which are particle effects related to the level type that, unlike
     19  * object-based particle effects, are rendered more efficiently by manually
     20  * generating display lists instead of drawing each particle separately.
     21  * This file implements snow effects, while in 'envfx_bubbles.c' the
     22  * implementation for flowers (unused), lava bubbles and jet stream bubbles
     23  * can be found.
     24  * The main entry point for envfx is at the bottom of this file, which is
     25  * called from geo_envfx_main in level_geo.c
     26  */
     27 
     28 // Might be duplicate
     29 struct SnowFlakeVertex {
     30     s16 x;
     31     s16 y;
     32     s16 z;
     33 };
     34 
     35 struct EnvFxParticle *gEnvFxBuffer;
     36 Vec3i gSnowCylinderLastPos;
     37 s16 gSnowParticleCount;
     38 s16 gSnowParticleMaxCount;
     39 
     40 /* DATA */
     41 s8 gEnvFxMode = ENVFX_MODE_NONE;
     42 UNUSED s32 D_80330644 = 0;
     43 
     44 /// Template for a snow particle triangle
     45 Vtx gSnowTempVtx[3] = { { { { -5, 5, 0 }, 0, { 0, 0 }, { 0x7F, 0x7F, 0x7F, 0xFF } } },
     46                         { { { -5, -5, 0 }, 0, { 0, 960 }, { 0x7F, 0x7F, 0x7F, 0xFF } } },
     47                         { { { 5, 5, 0 }, 0, { 960, 0 }, { 0x7F, 0x7F, 0x7F, 0xFF } } } };
     48 
     49 // Change these to make snowflakes smaller or bigger
     50 struct SnowFlakeVertex gSnowFlakeVertex1 = { -5, 5, 0 };
     51 struct SnowFlakeVertex gSnowFlakeVertex2 = { -5, -5, 0 };
     52 struct SnowFlakeVertex gSnowFlakeVertex3 = { 5, 5, 0 };
     53 
     54 extern void *tiny_bubble_dl_0B006AB0;
     55 extern void *tiny_bubble_dl_0B006A50;
     56 extern void *tiny_bubble_dl_0B006CD8;
     57 
     58 /**
     59  * Initialize snow particles by allocating a buffer for storing their state
     60  * and setting a start amount.
     61  */
     62 s32 envfx_init_snow(s32 mode) {
     63     switch (mode) {
     64         case ENVFX_MODE_NONE:
     65             return FALSE;
     66 
     67         case ENVFX_SNOW_NORMAL:
     68             gSnowParticleMaxCount = 140;
     69             gSnowParticleCount = 5;
     70             break;
     71 
     72         case ENVFX_SNOW_WATER:
     73             gSnowParticleMaxCount = 30;
     74             gSnowParticleCount = 30;
     75             break;
     76 
     77         case ENVFX_SNOW_BLIZZARD:
     78             gSnowParticleMaxCount = 140;
     79             gSnowParticleCount = 140;
     80             break;
     81     }
     82 
     83     gEnvFxBuffer = mem_pool_alloc(gEffectsMemoryPool,
     84                                   gSnowParticleMaxCount * sizeof(struct EnvFxParticle));
     85     if (gEnvFxBuffer == NULL) {
     86         return FALSE;
     87     }
     88 
     89     bzero(gEnvFxBuffer, gSnowParticleMaxCount * sizeof(struct EnvFxParticle));
     90 
     91     gEnvFxMode = mode;
     92     return TRUE;
     93 }
     94 
     95 /**
     96  * Update the amount of snow particles on screen.
     97  * Normal snow starts with few flakes and slowly increases to the maximum.
     98  * For water snow, this is dependent on how deep underwater you are.
     99  * Blizzard snows starts at the maximum amount and doesn't change.
    100  */
    101 void envfx_update_snowflake_count(s32 mode, Vec3s marioPos) {
    102     s32 globalTimer = gGlobalTimer;
    103     f32 waterLevel;
    104 
    105     switch (mode) {
    106         case ENVFX_SNOW_NORMAL:
    107             if (gSnowParticleMaxCount > gSnowParticleCount) {
    108                 if (!(globalTimer & 63)) {
    109                     gSnowParticleCount += 5;
    110                 }
    111             }
    112             break;
    113 
    114         case ENVFX_SNOW_WATER:
    115             waterLevel = find_water_level(marioPos[0], marioPos[2]);
    116 
    117             gSnowParticleCount =
    118                 (((s32)((waterLevel - 400.0f - (f32) marioPos[1]) * 0.001) << 0x10) >> 0x10) * 5;
    119 
    120             if (gSnowParticleCount < 0) {
    121                 gSnowParticleCount = 0;
    122             }
    123 
    124             if (gSnowParticleCount > gSnowParticleMaxCount) {
    125                 gSnowParticleCount = gSnowParticleMaxCount;
    126             }
    127 
    128             break;
    129 
    130         case ENVFX_SNOW_BLIZZARD:
    131             break;
    132     }
    133 }
    134 
    135 /**
    136  * Deallocate the buffer storing snow particles and set the environment effect
    137  * to none.
    138  */
    139 void envfx_cleanup_snow(void *snowParticleArray) {
    140     if (gEnvFxMode != ENVFX_MODE_NONE) {
    141         if (snowParticleArray) {
    142             mem_pool_free(gEffectsMemoryPool, snowParticleArray);
    143         }
    144         gEnvFxMode = ENVFX_MODE_NONE;
    145     }
    146 }
    147 
    148 /**
    149  * Given two points, return the vector from one to the other represented
    150  * as Euler angles and a length
    151  */
    152 void orbit_from_positions(Vec3s from, Vec3s to, s16 *radius, s16 *pitch, s16 *yaw) {
    153     f32 dx = to[0] - from[0];
    154     f32 dy = to[1] - from[1];
    155     f32 dz = to[2] - from[2];
    156 
    157     *radius = (s16) sqrtf(dx * dx + dy * dy + dz * dz);
    158     *pitch = atan2s(sqrtf(dx * dx + dz * dz), dy);
    159     *yaw = atan2s(dz, dx);
    160 }
    161 
    162 /**
    163  * Calculate the 'result' vector as the position of the 'origin' vector
    164  * with a vector added represented by radius, pitch and yaw.
    165  */
    166 void pos_from_orbit(Vec3s origin, Vec3s result, s16 radius, s16 pitch, s16 yaw) {
    167     result[0] = origin[0] + radius * coss(pitch) * sins(yaw);
    168     result[1] = origin[1] + radius * sins(pitch);
    169     result[2] = origin[2] + radius * coss(pitch) * coss(yaw);
    170 }
    171 
    172 /**
    173  * Check whether the snowflake with the given index is inside view, where
    174  * 'view' is a cylinder of radius 300 and height 400 centered at the input
    175  * x, y and z.
    176  */
    177 s32 envfx_is_snowflake_alive(s32 index, s32 snowCylinderX, s32 snowCylinderY, s32 snowCylinderZ) {
    178     s32 x = (gEnvFxBuffer + index)->xPos;
    179     s32 y = (gEnvFxBuffer + index)->yPos;
    180     s32 z = (gEnvFxBuffer + index)->zPos;
    181 
    182     if (sqr(x - snowCylinderX) + sqr(z - snowCylinderZ) > sqr(300)) {
    183         return FALSE;
    184     }
    185 
    186     if ((y < snowCylinderY - 201) || (snowCylinderY + 201 < y)) {
    187         return FALSE;
    188     }
    189 
    190     return TRUE;
    191 }
    192 
    193 /**
    194  * Update the position of each snowflake. Snowflakes wiggle by having a
    195  * random value added to their position each frame. If snowflakes get out
    196  * of view (where view = a small cylinder in front of the camera) their
    197  * position is reset to somewhere in view.
    198  * Since the cylinder of snow is so close to the camera, snow flakes would
    199  * move out of view very quickly when the camera moves. To mitigate this,
    200  * a portion of the difference between the previous and current snowCylinder
    201  * position is added to snowflakes to keep them in view for longer. That's
    202  * why the snow looks a bit off in 3d, it's a lot closer than you'd think
    203  * but appears to be further by means of hacky position updates. This might
    204  * have been done because larger, further away snowflakes are occluded easily
    205  * by level geometry, wasting many particles.
    206  */
    207 void envfx_update_snow_normal(s32 snowCylinderX, s32 snowCylinderY, s32 snowCylinderZ) {
    208     s32 i;
    209     s32 deltaX = snowCylinderX - gSnowCylinderLastPos[0];
    210     s32 deltaY = snowCylinderY - gSnowCylinderLastPos[1];
    211     s32 deltaZ = snowCylinderZ - gSnowCylinderLastPos[2];
    212 
    213     for (i = 0; i < gSnowParticleCount; i++) {
    214         (gEnvFxBuffer + i)->isAlive =
    215             envfx_is_snowflake_alive(i, snowCylinderX, snowCylinderY, snowCylinderZ);
    216         if (!(gEnvFxBuffer + i)->isAlive) {
    217             (gEnvFxBuffer + i)->xPos =
    218                 400.0f * random_float() - 200.0f + snowCylinderX + (s16)(deltaX * 2);
    219             (gEnvFxBuffer + i)->zPos =
    220                 400.0f * random_float() - 200.0f + snowCylinderZ + (s16)(deltaZ * 2);
    221             (gEnvFxBuffer + i)->yPos = 200.0f * random_float() + snowCylinderY;
    222             (gEnvFxBuffer + i)->isAlive = TRUE;
    223         } else {
    224             (gEnvFxBuffer + i)->xPos += random_float() * 2 - 1.0f + (s16)(deltaX / 1.2);
    225             (gEnvFxBuffer + i)->yPos -= 2 -(s16)(deltaY * 0.8);
    226             (gEnvFxBuffer + i)->zPos += random_float() * 2 - 1.0f + (s16)(deltaZ / 1.2);
    227         }
    228     }
    229 
    230     gSnowCylinderLastPos[0] = snowCylinderX;
    231     gSnowCylinderLastPos[1] = snowCylinderY;
    232     gSnowCylinderLastPos[2] = snowCylinderZ;
    233 }
    234 
    235 /**
    236  * Unused function. Basically a copy-paste of envfx_update_snow_normal,
    237  * but an extra 20 units is added to each snowflake x and snowflakes can
    238  * respawn in y-range [-200, 200] instead of [0, 200] relative to snowCylinderY
    239  * They also fall a bit faster (with vertical speed -5 instead of -2).
    240  */
    241 void envfx_update_snow_blizzard(s32 snowCylinderX, s32 snowCylinderY, s32 snowCylinderZ) {
    242     s32 i;
    243     s32 deltaX = snowCylinderX - gSnowCylinderLastPos[0];
    244     s32 deltaY = snowCylinderY - gSnowCylinderLastPos[1];
    245     s32 deltaZ = snowCylinderZ - gSnowCylinderLastPos[2];
    246 
    247     for (i = 0; i < gSnowParticleCount; i++) {
    248         (gEnvFxBuffer + i)->isAlive =
    249             envfx_is_snowflake_alive(i, snowCylinderX, snowCylinderY, snowCylinderZ);
    250         if (!(gEnvFxBuffer + i)->isAlive) {
    251             (gEnvFxBuffer + i)->xPos =
    252                 400.0f * random_float() - 200.0f + snowCylinderX + (s16)(deltaX * 2);
    253             (gEnvFxBuffer + i)->zPos =
    254                 400.0f * random_float() - 200.0f + snowCylinderZ + (s16)(deltaZ * 2);
    255             (gEnvFxBuffer + i)->yPos = 400.0f * random_float() - 200.0f + snowCylinderY;
    256             (gEnvFxBuffer + i)->isAlive = TRUE;
    257         } else {
    258             (gEnvFxBuffer + i)->xPos += random_float() * 2 - 1.0f + (s16)(deltaX / 1.2) + 20.0f;
    259             (gEnvFxBuffer + i)->yPos -= 5 -(s16)(deltaY * 0.8);
    260             (gEnvFxBuffer + i)->zPos += random_float() * 2 - 1.0f + (s16)(deltaZ / 1.2);
    261         }
    262     }
    263 
    264     gSnowCylinderLastPos[0] = snowCylinderX;
    265     gSnowCylinderLastPos[1] = snowCylinderY;
    266     gSnowCylinderLastPos[2] = snowCylinderZ;
    267 }
    268 
    269 /*! Unused function. Checks whether a position is laterally within 3000 units
    270  *  to the point (x: 3380, z: -520). Considering there is an unused blizzard
    271  *  snow mode, this could have been used to check whether Mario is in a
    272  *  'blizzard area'. In Cool, Cool Mountain and Snowman's Land the area lies
    273  *  near the starting point and doesn't seem meaningful. Notably, the point is
    274  *  close to the entrance of SL, so maybe there were plans for an extra hint to
    275  *  find it. The radius of 3000 units is quite large for that though, covering
    276  *  more than half of the mirror room.
    277  */
    278 UNUSED static s32 is_in_mystery_snow_area(s32 x, UNUSED s32 y, s32 z) {
    279     if (sqr(x - 3380) + sqr(z + 520) < sqr(3000)) {
    280         return TRUE;
    281     }
    282     return FALSE;
    283 }
    284 
    285 /**
    286  * Update the position of underwater snow particles. Since they are stationary,
    287  * they merely jump back into view when they are out of view.
    288  */
    289 void envfx_update_snow_water(s32 snowCylinderX, s32 snowCylinderY, s32 snowCylinderZ) {
    290     s32 i;
    291 
    292     for (i = 0; i < gSnowParticleCount; i++) {
    293         (gEnvFxBuffer + i)->isAlive =
    294             envfx_is_snowflake_alive(i, snowCylinderX, snowCylinderY, snowCylinderZ);
    295         if (!(gEnvFxBuffer + i)->isAlive) {
    296             (gEnvFxBuffer + i)->xPos = 400.0f * random_float() - 200.0f + snowCylinderX;
    297             (gEnvFxBuffer + i)->zPos = 400.0f * random_float() - 200.0f + snowCylinderZ;
    298             (gEnvFxBuffer + i)->yPos = 400.0f * random_float() - 200.0f + snowCylinderY;
    299             (gEnvFxBuffer + i)->isAlive = TRUE;
    300         }
    301     }
    302 }
    303 
    304 /**
    305  * Rotates the input vertices according to the give pitch and yaw. This
    306  * is needed for billboarding of particles.
    307  */
    308 void rotate_triangle_vertices(Vec3s vertex1, Vec3s vertex2, Vec3s vertex3, s16 pitch, s16 yaw) {
    309     f32 cosPitch = coss(pitch);
    310     f32 sinPitch = sins(pitch);
    311     f32 cosMYaw = coss(-yaw);
    312     f32 sinMYaw = sins(-yaw);
    313 
    314     Vec3f v1, v2, v3;
    315 
    316     v1[0] = vertex1[0];
    317     v1[1] = vertex1[1];
    318     v1[2] = vertex1[2];
    319 
    320     v2[0] = vertex2[0];
    321     v2[1] = vertex2[1];
    322     v2[2] = vertex2[2];
    323 
    324     v3[0] = vertex3[0];
    325     v3[1] = vertex3[1];
    326     v3[2] = vertex3[2];
    327 
    328     vertex1[0] = v1[0] * cosMYaw + v1[1] * (sinPitch * sinMYaw) + v1[2] * (-sinMYaw * cosPitch);
    329     vertex1[1] = v1[1] * cosPitch + v1[2] * sinPitch;
    330     vertex1[2] = v1[0] * sinMYaw + v1[1] * (-sinPitch * cosMYaw) + v1[2] * (cosPitch * cosMYaw);
    331 
    332     vertex2[0] = v2[0] * cosMYaw + v2[1] * (sinPitch * sinMYaw) + v2[2] * (-sinMYaw * cosPitch);
    333     vertex2[1] = v2[1] * cosPitch + v2[2] * sinPitch;
    334     vertex2[2] = v2[0] * sinMYaw + v2[1] * (-sinPitch * cosMYaw) + v2[2] * (cosPitch * cosMYaw);
    335 
    336     vertex3[0] = v3[0] * cosMYaw + v3[1] * (sinPitch * sinMYaw) + v3[2] * (-sinMYaw * cosPitch);
    337     vertex3[1] = v3[1] * cosPitch + v3[2] * sinPitch;
    338     vertex3[2] = v3[0] * sinMYaw + v3[1] * (-sinPitch * cosMYaw) + v3[2] * (cosPitch * cosMYaw);
    339 }
    340 
    341 /**
    342  * Append 15 vertices to 'gfx', which is enough for 5 snowflakes starting at
    343  * 'index' in the buffer. The 3 input vertices represent the rotated triangle
    344  * around (0,0,0) that will be translated to snowflake positions to draw the
    345  * snowflake image.
    346  */
    347 void append_snowflake_vertex_buffer(Gfx *gfx, s32 index, Vec3s vertex1, Vec3s vertex2, Vec3s vertex3) {
    348     s32 i = 0;
    349     Vtx *vertBuf = (Vtx *) alloc_display_list(15 * sizeof(Vtx));
    350 
    351     if (vertBuf == NULL) {
    352         return;
    353     }
    354 
    355     for (i = 0; i < 15; i += 3) {
    356         vertBuf[i] = gSnowTempVtx[0];
    357         (vertBuf + i)->v.ob[0] = (gEnvFxBuffer + (index + i / 3))->xPos + vertex1[0];
    358         (vertBuf + i)->v.ob[1] = (gEnvFxBuffer + (index + i / 3))->yPos + vertex1[1];
    359         (vertBuf + i)->v.ob[2] = (gEnvFxBuffer + (index + i / 3))->zPos + vertex1[2];
    360 
    361         vertBuf[i + 1] = gSnowTempVtx[1];
    362         (vertBuf + i + 1)->v.ob[0] = (gEnvFxBuffer + (index + i / 3))->xPos + vertex2[0];
    363         (vertBuf + i + 1)->v.ob[1] = (gEnvFxBuffer + (index + i / 3))->yPos + vertex2[1];
    364         (vertBuf + i + 1)->v.ob[2] = (gEnvFxBuffer + (index + i / 3))->zPos + vertex2[2];
    365 
    366         vertBuf[i + 2] = gSnowTempVtx[2];
    367         (vertBuf + i + 2)->v.ob[0] = (gEnvFxBuffer + (index + i / 3))->xPos + vertex3[0];
    368         (vertBuf + i + 2)->v.ob[1] = (gEnvFxBuffer + (index + i / 3))->yPos + vertex3[1];
    369         (vertBuf + i + 2)->v.ob[2] = (gEnvFxBuffer + (index + i / 3))->zPos + vertex3[2];
    370     }
    371 
    372     gSPVertex(gfx, VIRTUAL_TO_PHYSICAL(vertBuf), 15, 0);
    373 }
    374 
    375 /**
    376  * Updates positions of snow particles and returns a pointer to a display list
    377  * drawing all snowflakes.
    378  */
    379 Gfx *envfx_update_snow(s32 snowMode, Vec3s marioPos, Vec3s camFrom, Vec3s camTo) {
    380     s32 i;
    381     s16 radius, pitch, yaw;
    382     Vec3s snowCylinderPos;
    383     struct SnowFlakeVertex vertex1, vertex2, vertex3;
    384     Gfx *gfxStart;
    385     Gfx *gfx;
    386 
    387     vertex1 = gSnowFlakeVertex1;
    388     vertex2 = gSnowFlakeVertex2;
    389     vertex3 = gSnowFlakeVertex3;
    390 
    391     gfxStart = (Gfx *) alloc_display_list((gSnowParticleCount * 6 + 3) * sizeof(Gfx));
    392     gfx = gfxStart;
    393 
    394     if (gfxStart == NULL) {
    395         return NULL;
    396     }
    397 
    398     envfx_update_snowflake_count(snowMode, marioPos);
    399 
    400     // Note: to and from are inverted here, so the resulting vector goes towards the camera
    401     orbit_from_positions(camTo, camFrom, &radius, &pitch, &yaw);
    402 
    403     switch (snowMode) {
    404         case ENVFX_SNOW_NORMAL:
    405             // ensure the snow cylinder is no further than 250 units in front
    406             // of the camera, and no closer than 1 unit.
    407             if (radius > 250) {
    408                 radius -= 250;
    409             } else {
    410                 radius = 1;
    411             }
    412 
    413             pos_from_orbit(camTo, snowCylinderPos, radius, pitch, yaw);
    414             envfx_update_snow_normal(snowCylinderPos[0], snowCylinderPos[1], snowCylinderPos[2]);
    415             break;
    416 
    417         case ENVFX_SNOW_WATER:
    418             if (radius > 500) {
    419                 radius -= 500;
    420             } else {
    421                 radius = 1;
    422             }
    423 
    424             pos_from_orbit(camTo, snowCylinderPos, radius, pitch, yaw);
    425             envfx_update_snow_water(snowCylinderPos[0], snowCylinderPos[1], snowCylinderPos[2]);
    426             break;
    427         case ENVFX_SNOW_BLIZZARD:
    428             if (radius > 250) {
    429                 radius -= 250;
    430             } else {
    431                 radius = 1;
    432             }
    433 
    434             pos_from_orbit(camTo, snowCylinderPos, radius, pitch, yaw);
    435             envfx_update_snow_blizzard(snowCylinderPos[0], snowCylinderPos[1], snowCylinderPos[2]);
    436             break;
    437     }
    438 
    439     rotate_triangle_vertices((s16 *) &vertex1, (s16 *) &vertex2, (s16 *) &vertex3, pitch, yaw);
    440 
    441     if (snowMode == ENVFX_SNOW_NORMAL || snowMode == ENVFX_SNOW_BLIZZARD) {
    442         gSPDisplayList(gfx++, &tiny_bubble_dl_0B006A50); // snowflake with gray edge
    443     } else if (snowMode == ENVFX_SNOW_WATER) {
    444         gSPDisplayList(gfx++, &tiny_bubble_dl_0B006CD8); // snowflake with blue edge
    445     }
    446 
    447     for (i = 0; i < gSnowParticleCount; i += 5) {
    448         append_snowflake_vertex_buffer(gfx++, i, (s16 *) &vertex1, (s16 *) &vertex2, (s16 *) &vertex3);
    449 
    450         gSP1Triangle(gfx++, 0, 1, 2, 0);
    451         gSP1Triangle(gfx++, 3, 4, 5, 0);
    452         gSP1Triangle(gfx++, 6, 7, 8, 0);
    453         gSP1Triangle(gfx++, 9, 10, 11, 0);
    454         gSP1Triangle(gfx++, 12, 13, 14, 0);
    455     }
    456 
    457     gSPDisplayList(gfx++, &tiny_bubble_dl_0B006AB0) gSPEndDisplayList(gfx++);
    458 
    459     return gfxStart;
    460 }
    461 
    462 /**
    463  * Updates the environment effects (snow, flowers, bubbles)
    464  * and returns a display list drawing them.
    465  */
    466 Gfx *envfx_update_particles(s32 mode, Vec3s marioPos, Vec3s camTo, Vec3s camFrom) {
    467     Gfx *gfx;
    468 
    469     if (get_dialog_id() != DIALOG_NONE) {
    470         return NULL;
    471     }
    472 
    473     if (gEnvFxMode != ENVFX_MODE_NONE && gEnvFxMode != mode) {
    474         mode = ENVFX_MODE_NONE;
    475     }
    476 
    477     if (mode >= ENVFX_BUBBLE_START) {
    478         gfx = envfx_update_bubbles(mode, marioPos, camTo, camFrom);
    479         return gfx;
    480     }
    481 
    482     if (gEnvFxMode == ENVFX_MODE_NONE && !envfx_init_snow(mode)) {
    483         return NULL;
    484     }
    485 
    486     switch (mode) {
    487         case ENVFX_MODE_NONE:
    488             envfx_cleanup_snow(gEnvFxBuffer);
    489             return NULL;
    490 
    491         case ENVFX_SNOW_NORMAL:
    492             gfx = envfx_update_snow(1, marioPos, camFrom, camTo);
    493             break;
    494 
    495         case ENVFX_SNOW_WATER:
    496             gfx = envfx_update_snow(2, marioPos, camFrom, camTo);
    497             break;
    498 
    499         case ENVFX_SNOW_BLIZZARD:
    500             gfx = envfx_update_snow(3, marioPos, camFrom, camTo);
    501             break;
    502 
    503         default:
    504             return NULL;
    505     }
    506 
    507     return gfx;
    508 }