Advertisement
Guest User

envfx_snow.c

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