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 }