2019-08-25 04:46:40 +00:00
|
|
|
#include <ultra64.h>
|
|
|
|
|
|
|
|
#include "sm64.h"
|
|
|
|
#include "display.h"
|
|
|
|
#include "memory.h"
|
|
|
|
#include "ingame_menu.h"
|
|
|
|
#include "envfx_snow.h"
|
|
|
|
#include "envfx_bubbles.h"
|
|
|
|
#include "engine/surface_collision.h"
|
|
|
|
#include "engine/math_util.h"
|
|
|
|
#include "engine/behavior_script.h"
|
|
|
|
#include "audio/external.h"
|
|
|
|
#include "obj_behaviors.h"
|
|
|
|
|
2019-09-01 19:50:50 +00:00
|
|
|
/**
|
|
|
|
* This file contains the function that handles 'environment effects',
|
|
|
|
* which are particle effects related to the level type that, unlike
|
|
|
|
* object-based particle effects, are rendered more efficiently by manually
|
|
|
|
* generating display lists instead of drawing each particle separately.
|
|
|
|
* This file implements snow effects, while in 'envfx_bubbles.c' the
|
|
|
|
* implementation for flowers (unused), lava bubbles and jetstream bubbles
|
|
|
|
* can be found.
|
|
|
|
* The main entry point for envfx is at the bottom of this file, which is
|
2020-03-02 03:42:52 +00:00
|
|
|
* called from geo_envfx_main in level_geo.c
|
2019-08-25 04:46:40 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
// Might be duplicate
|
|
|
|
struct SnowFlakeVertex {
|
|
|
|
s16 x;
|
|
|
|
s16 y;
|
|
|
|
s16 z;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct EnvFxParticle *gEnvFxBuffer;
|
|
|
|
Vec3i gSnowCylinderLastPos;
|
|
|
|
s16 gSnowParticleCount;
|
|
|
|
s16 gSnowParticleMaxCount;
|
|
|
|
|
|
|
|
/* DATA */
|
|
|
|
s8 gEnvFxMode = 0;
|
|
|
|
UNUSED s32 D_80330644 = 0;
|
|
|
|
|
|
|
|
/// Template for a snow particle triangle
|
|
|
|
Vtx gSnowTempVtx[3] = { { { { -5, 5, 0 }, 0, { 0, 0 }, { 0x7F, 0x7F, 0x7F, 0xFF } } },
|
|
|
|
{ { { -5, -5, 0 }, 0, { 0, 960 }, { 0x7F, 0x7F, 0x7F, 0xFF } } },
|
|
|
|
{ { { 5, 5, 0 }, 0, { 960, 0 }, { 0x7F, 0x7F, 0x7F, 0xFF } } } };
|
|
|
|
|
|
|
|
// Change these to make snowflakes smaller or bigger
|
|
|
|
struct SnowFlakeVertex gSnowFlakeVertex1 = { -5, 5, 0 };
|
|
|
|
struct SnowFlakeVertex gSnowFlakeVertex2 = { -5, -5, 0 };
|
|
|
|
struct SnowFlakeVertex gSnowFlakeVertex3 = { 5, 5, 0 };
|
|
|
|
|
|
|
|
extern void *tiny_bubble_dl_0B006AB0;
|
|
|
|
extern void *tiny_bubble_dl_0B006A50;
|
|
|
|
extern void *tiny_bubble_dl_0B006CD8;
|
|
|
|
|
2019-09-01 19:50:50 +00:00
|
|
|
/**
|
|
|
|
* Initialize snow particles by allocating a buffer for storing their state
|
|
|
|
* and setting a start amount.
|
2019-08-25 04:46:40 +00:00
|
|
|
*/
|
|
|
|
s32 envfx_init_snow(s32 mode) {
|
|
|
|
switch (mode) {
|
|
|
|
case ENVFX_MODE_NONE:
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
case ENVFX_SNOW_NORMAL:
|
|
|
|
gSnowParticleMaxCount = 140;
|
|
|
|
gSnowParticleCount = 5;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ENVFX_SNOW_WATER:
|
|
|
|
gSnowParticleMaxCount = 30;
|
|
|
|
gSnowParticleCount = 30;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ENVFX_SNOW_BLIZZARD:
|
|
|
|
gSnowParticleMaxCount = 140;
|
|
|
|
gSnowParticleCount = 140;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2020-01-03 15:38:57 +00:00
|
|
|
gEnvFxBuffer = mem_pool_alloc(gEffectsMemoryPool, gSnowParticleMaxCount * sizeof(struct EnvFxParticle));
|
2019-09-01 19:50:50 +00:00
|
|
|
if (!gEnvFxBuffer) {
|
2019-08-25 04:46:40 +00:00
|
|
|
return 0;
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
2019-08-25 04:46:40 +00:00
|
|
|
|
|
|
|
bzero(gEnvFxBuffer, gSnowParticleMaxCount * sizeof(struct EnvFxParticle));
|
|
|
|
|
|
|
|
gEnvFxMode = mode;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2019-09-01 19:50:50 +00:00
|
|
|
/**
|
|
|
|
* Update the amount of snow particles on screen.
|
|
|
|
* Normal snow starts with few flakes and slowly increases to the maximum.
|
|
|
|
* For water snow, this is dependent on how deep underwater you are.
|
|
|
|
* Blizzard snows starts at the maximum amount and doesn't change.
|
2019-08-25 04:46:40 +00:00
|
|
|
*/
|
|
|
|
void envfx_update_snowflake_count(s32 mode, Vec3s marioPos) {
|
|
|
|
s32 timer = gGlobalTimer;
|
|
|
|
f32 waterLevel;
|
|
|
|
switch (mode) {
|
|
|
|
case ENVFX_SNOW_NORMAL:
|
|
|
|
if (gSnowParticleMaxCount > gSnowParticleCount) {
|
2019-09-01 19:50:50 +00:00
|
|
|
if ((timer & 0x3F) == 0) {
|
2019-08-25 04:46:40 +00:00
|
|
|
gSnowParticleCount += 5;
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
2019-08-25 04:46:40 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ENVFX_SNOW_WATER:
|
|
|
|
waterLevel = find_water_level(marioPos[0], marioPos[2]);
|
|
|
|
|
|
|
|
gSnowParticleCount =
|
|
|
|
(((s32)((waterLevel - 400.f - (f32) marioPos[1]) * 1.0e-3) << 0x10) >> 0x10) * 5;
|
|
|
|
|
2019-09-01 19:50:50 +00:00
|
|
|
if (gSnowParticleCount < 0) {
|
2019-08-25 04:46:40 +00:00
|
|
|
gSnowParticleCount = 0;
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
2019-08-25 04:46:40 +00:00
|
|
|
|
2019-09-01 19:50:50 +00:00
|
|
|
if (gSnowParticleCount > gSnowParticleMaxCount) {
|
2019-08-25 04:46:40 +00:00
|
|
|
gSnowParticleCount = gSnowParticleMaxCount;
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
2019-08-25 04:46:40 +00:00
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ENVFX_SNOW_BLIZZARD:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-01 19:50:50 +00:00
|
|
|
/**
|
|
|
|
* Deallocate the buffer storing snow particles and set the environment effect
|
|
|
|
* to none.
|
2019-08-25 04:46:40 +00:00
|
|
|
*/
|
|
|
|
void envfx_cleanup_snow(void *snowParticleArray) {
|
|
|
|
if (gEnvFxMode) {
|
|
|
|
if (snowParticleArray) {
|
2020-01-03 15:38:57 +00:00
|
|
|
mem_pool_free(gEffectsMemoryPool, snowParticleArray);
|
2019-08-25 04:46:40 +00:00
|
|
|
}
|
|
|
|
gEnvFxMode = ENVFX_MODE_NONE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-01 19:50:50 +00:00
|
|
|
/**
|
|
|
|
* Given two points, return the vector from one to the other represented
|
|
|
|
* as euler angles and a length
|
2019-08-25 04:46:40 +00:00
|
|
|
*/
|
|
|
|
void orbit_from_positions(Vec3s from, Vec3s to, s16 *radius, s16 *pitch, s16 *yaw) {
|
|
|
|
f32 dx = to[0] - from[0];
|
|
|
|
f32 dy = to[1] - from[1];
|
|
|
|
f32 dz = to[2] - from[2];
|
|
|
|
|
|
|
|
*radius = (s16) sqrtf(dx * dx + dy * dy + dz * dz);
|
|
|
|
*pitch = atan2s(sqrtf(dx * dx + dz * dz), dy);
|
|
|
|
*yaw = atan2s(dz, dx);
|
|
|
|
}
|
|
|
|
|
2019-09-01 19:50:50 +00:00
|
|
|
/**
|
|
|
|
* Calculate the 'result' vector as the position of the 'origin' vector
|
|
|
|
* with a vector added represented by radius, pitch and yaw.
|
2019-08-25 04:46:40 +00:00
|
|
|
*/
|
|
|
|
void pos_from_orbit(Vec3s origin, Vec3s result, s16 radius, s16 pitch, s16 yaw) {
|
|
|
|
result[0] = origin[0] + radius * coss(pitch) * sins(yaw);
|
|
|
|
result[1] = origin[1] + radius * sins(pitch);
|
|
|
|
result[2] = origin[2] + radius * coss(pitch) * coss(yaw);
|
|
|
|
}
|
|
|
|
|
2019-09-01 19:50:50 +00:00
|
|
|
/**
|
|
|
|
* Check whether the snowflake with the given index is inside view, where
|
|
|
|
* 'view' is a cylinder of radius 300 and height 400 centered at the input
|
|
|
|
* x, y and z.
|
2019-08-25 04:46:40 +00:00
|
|
|
*/
|
|
|
|
s32 envfx_is_snowflake_alive(s32 index, s32 snowCylinderX, s32 snowCylinderY, s32 snowCylinderZ) {
|
|
|
|
s32 x = (gEnvFxBuffer + index)->xPos;
|
|
|
|
s32 y = (gEnvFxBuffer + index)->yPos;
|
|
|
|
s32 z = (gEnvFxBuffer + index)->zPos;
|
|
|
|
|
2019-09-01 19:50:50 +00:00
|
|
|
if (sqr(x - snowCylinderX) + sqr(z - snowCylinderZ) > sqr(300)) {
|
2019-08-25 04:46:40 +00:00
|
|
|
return 0;
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
2019-08-25 04:46:40 +00:00
|
|
|
|
2019-09-01 19:50:50 +00:00
|
|
|
if ((y < snowCylinderY - 201) || (snowCylinderY + 201 < y)) {
|
2019-08-25 04:46:40 +00:00
|
|
|
return 0;
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
2019-08-25 04:46:40 +00:00
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2019-09-01 19:50:50 +00:00
|
|
|
/**
|
|
|
|
* Update the position of each snowflake. Snowflakes wiggle by having a
|
|
|
|
* random value added to their position each frame. If snowflakes get out
|
|
|
|
* of view (where view = a small cylinder in front of the camera) their
|
|
|
|
* position is reset to somewhere in view.
|
|
|
|
* Since the cylinder of snow is so close to the camera, snow flakes would
|
|
|
|
* move out of view very quickly when the camera moves. To mitigate this,
|
|
|
|
* a portion of the difference between the previous and current snowCylinder
|
|
|
|
* position is added to snowflakes to keep them in view for longer. That's
|
|
|
|
* why the snow looks a bit off in 3d, it's a lot closer than you'd think
|
|
|
|
* but appears to be further by means of hacky position updates. This might
|
|
|
|
* have been done because larger, further away snowflakes are occluded easily
|
|
|
|
* by level geometry, wasting many particles.
|
2019-08-25 04:46:40 +00:00
|
|
|
*/
|
|
|
|
void envfx_update_snow_normal(s32 snowCylinderX, s32 snowCylinderY, s32 snowCylinderZ) {
|
|
|
|
s32 i;
|
|
|
|
s32 deltaX = snowCylinderX - gSnowCylinderLastPos[0];
|
|
|
|
s32 deltaY = snowCylinderY - gSnowCylinderLastPos[1];
|
|
|
|
s32 deltaZ = snowCylinderZ - gSnowCylinderLastPos[2];
|
|
|
|
|
|
|
|
for (i = 0; i < gSnowParticleCount; i++) {
|
|
|
|
(gEnvFxBuffer + i)->isAlive =
|
|
|
|
envfx_is_snowflake_alive(i, snowCylinderX, snowCylinderY, snowCylinderZ);
|
|
|
|
if ((gEnvFxBuffer + i)->isAlive == 0) {
|
|
|
|
(gEnvFxBuffer + i)->xPos =
|
|
|
|
400.0f * RandomFloat() - 200.0f + snowCylinderX + (s16)(deltaX * 2);
|
|
|
|
(gEnvFxBuffer + i)->zPos =
|
|
|
|
400.0f * RandomFloat() - 200.0f + snowCylinderZ + (s16)(deltaZ * 2);
|
|
|
|
(gEnvFxBuffer + i)->yPos = 200.0f * RandomFloat() + snowCylinderY;
|
|
|
|
(gEnvFxBuffer + i)->isAlive = 1;
|
|
|
|
} else {
|
|
|
|
(gEnvFxBuffer + i)->xPos += RandomFloat() * 2 - 1.0f + (s16)(deltaX / 1.2);
|
2020-02-03 05:51:26 +00:00
|
|
|
(gEnvFxBuffer + i)->yPos -= 2 -(s16)(deltaY * 0.8);
|
2019-08-25 04:46:40 +00:00
|
|
|
(gEnvFxBuffer + i)->zPos += RandomFloat() * 2 - 1.0f + (s16)(deltaZ / 1.2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
gSnowCylinderLastPos[0] = snowCylinderX;
|
|
|
|
gSnowCylinderLastPos[1] = snowCylinderY;
|
|
|
|
gSnowCylinderLastPos[2] = snowCylinderZ;
|
|
|
|
}
|
|
|
|
|
2019-09-01 19:50:50 +00:00
|
|
|
/**
|
|
|
|
* Unused function. Basically a copy-paste of envfx_update_snow_normal,
|
|
|
|
* but an extra 20 units is added to each snowflake x and snowflakes can
|
|
|
|
* respawn in y-range [-200, 200] instead of [0, 200] relative to snowCylinderY
|
|
|
|
* They also fall a bit faster (with vertical speed -5 instead of -2).
|
2019-08-25 04:46:40 +00:00
|
|
|
*/
|
|
|
|
void envfx_update_snow_blizzard(s32 snowCylinderX, s32 snowCylinderY, s32 snowCylinderZ) {
|
|
|
|
s32 i;
|
|
|
|
s32 deltaX = snowCylinderX - gSnowCylinderLastPos[0];
|
|
|
|
s32 deltaY = snowCylinderY - gSnowCylinderLastPos[1];
|
|
|
|
s32 deltaZ = snowCylinderZ - gSnowCylinderLastPos[2];
|
|
|
|
|
|
|
|
for (i = 0; i < gSnowParticleCount; i++) {
|
|
|
|
(gEnvFxBuffer + i)->isAlive =
|
|
|
|
envfx_is_snowflake_alive(i, snowCylinderX, snowCylinderY, snowCylinderZ);
|
|
|
|
if ((gEnvFxBuffer + i)->isAlive == 0) {
|
|
|
|
(gEnvFxBuffer + i)->xPos =
|
|
|
|
400.0f * RandomFloat() - 200.0f + snowCylinderX + (s16)(deltaX * 2);
|
|
|
|
(gEnvFxBuffer + i)->zPos =
|
|
|
|
400.0f * RandomFloat() - 200.0f + snowCylinderZ + (s16)(deltaZ * 2);
|
|
|
|
(gEnvFxBuffer + i)->yPos = 400.0f * RandomFloat() - 200.0f + snowCylinderY;
|
|
|
|
(gEnvFxBuffer + i)->isAlive = 1;
|
|
|
|
} else {
|
|
|
|
(gEnvFxBuffer + i)->xPos += RandomFloat() * 2 - 1.0f + (s16)(deltaX / 1.2) + 20.0f;
|
2020-02-03 05:51:26 +00:00
|
|
|
(gEnvFxBuffer + i)->yPos -= 5 -(s16)(deltaY * 0.8);
|
2019-08-25 04:46:40 +00:00
|
|
|
(gEnvFxBuffer + i)->zPos += RandomFloat() * 2 - 1.0f + (s16)(deltaZ / 1.2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
gSnowCylinderLastPos[0] = snowCylinderX;
|
|
|
|
gSnowCylinderLastPos[1] = snowCylinderY;
|
|
|
|
gSnowCylinderLastPos[2] = snowCylinderZ;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*! Unused function. Checks whether a position is laterally within 3000 units
|
|
|
|
* to the point (x: 3380, z: -520). Considering there is an unused blizzard
|
|
|
|
* snow mode, this could have been used to check whether Mario is in a
|
|
|
|
* 'blizzard area'. In Cool Cool Mountain and Snowman's Land the area lies
|
|
|
|
* near the starting point and doesn't seem meaningfull. Notably, the point is
|
|
|
|
* close to the entrance of SL, so maybe there were plans for an extra hint to
|
|
|
|
* find it. The radius of 3000 units is quite large for that though, covering
|
|
|
|
* more than half of the mirror room.
|
|
|
|
*/
|
|
|
|
static s32 is_in_mystery_snow_area(s32 x, UNUSED s32 y, s32 z) {
|
2019-09-01 19:50:50 +00:00
|
|
|
if (sqr(x - 3380) + sqr(z + 520) < sqr(3000)) {
|
2019-08-25 04:46:40 +00:00
|
|
|
return 1;
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
2019-08-25 04:46:40 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-09-01 19:50:50 +00:00
|
|
|
/**
|
|
|
|
* Update the position of underwater snow particles. Since they are stationary,
|
|
|
|
* they merely jump back into view when they are out of view.
|
2019-08-25 04:46:40 +00:00
|
|
|
*/
|
|
|
|
void envfx_update_snow_water(s32 snowCylinderX, s32 snowCylinderY, s32 snowCylinderZ) {
|
|
|
|
s32 i;
|
|
|
|
|
|
|
|
for (i = 0; i < gSnowParticleCount; i++) {
|
|
|
|
(gEnvFxBuffer + i)->isAlive =
|
|
|
|
envfx_is_snowflake_alive(i, snowCylinderX, snowCylinderY, snowCylinderZ);
|
|
|
|
if ((gEnvFxBuffer + i)->isAlive == 0) {
|
|
|
|
(gEnvFxBuffer + i)->xPos = 400.0f * RandomFloat() - 200.0f + snowCylinderX;
|
|
|
|
(gEnvFxBuffer + i)->zPos = 400.0f * RandomFloat() - 200.0f + snowCylinderZ;
|
|
|
|
(gEnvFxBuffer + i)->yPos = 400.0f * RandomFloat() - 200.0f + snowCylinderY;
|
|
|
|
(gEnvFxBuffer + i)->isAlive = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-01 19:50:50 +00:00
|
|
|
/**
|
|
|
|
* Rotates the input vertices according to the give pitch and yaw. This
|
|
|
|
* is needed for billboarding of particles.
|
2019-08-25 04:46:40 +00:00
|
|
|
*/
|
|
|
|
void rotate_triangle_vertices(Vec3s vertex1, Vec3s vertex2, Vec3s vertex3, s16 pitch, s16 yaw) {
|
|
|
|
f32 cosPitch = coss(pitch);
|
|
|
|
f32 sinPitch = sins(pitch);
|
|
|
|
f32 cosMYaw = coss(-yaw);
|
|
|
|
f32 sinMYaw = sins(-yaw);
|
|
|
|
|
|
|
|
Vec3f v1, v2, v3;
|
|
|
|
|
|
|
|
v1[0] = vertex1[0];
|
|
|
|
v1[1] = vertex1[1];
|
|
|
|
v1[2] = vertex1[2];
|
|
|
|
|
|
|
|
v2[0] = vertex2[0];
|
|
|
|
v2[1] = vertex2[1];
|
|
|
|
v2[2] = vertex2[2];
|
|
|
|
|
|
|
|
v3[0] = vertex3[0];
|
|
|
|
v3[1] = vertex3[1];
|
|
|
|
v3[2] = vertex3[2];
|
|
|
|
|
|
|
|
vertex1[0] = v1[0] * cosMYaw + v1[1] * (sinPitch * sinMYaw) + v1[2] * (-sinMYaw * cosPitch);
|
|
|
|
vertex1[1] = v1[1] * cosPitch + v1[2] * sinPitch;
|
|
|
|
vertex1[2] = v1[0] * sinMYaw + v1[1] * (-sinPitch * cosMYaw) + v1[2] * (cosPitch * cosMYaw);
|
|
|
|
|
|
|
|
vertex2[0] = v2[0] * cosMYaw + v2[1] * (sinPitch * sinMYaw) + v2[2] * (-sinMYaw * cosPitch);
|
|
|
|
vertex2[1] = v2[1] * cosPitch + v2[2] * sinPitch;
|
|
|
|
vertex2[2] = v2[0] * sinMYaw + v2[1] * (-sinPitch * cosMYaw) + v2[2] * (cosPitch * cosMYaw);
|
|
|
|
|
|
|
|
vertex3[0] = v3[0] * cosMYaw + v3[1] * (sinPitch * sinMYaw) + v3[2] * (-sinMYaw * cosPitch);
|
|
|
|
vertex3[1] = v3[1] * cosPitch + v3[2] * sinPitch;
|
|
|
|
vertex3[2] = v3[0] * sinMYaw + v3[1] * (-sinPitch * cosMYaw) + v3[2] * (cosPitch * cosMYaw);
|
|
|
|
}
|
|
|
|
|
2019-09-01 19:50:50 +00:00
|
|
|
/**
|
|
|
|
* Append 15 vertices to 'gfx', which is enough for 5 snowflakes starting at
|
|
|
|
* 'index' in the buffer. The 3 input vertices represent the roated triangle
|
|
|
|
* around (0,0,0) that will be translated to snowflake positions to draw the
|
|
|
|
* snowflake image.
|
2020-03-02 03:42:52 +00:00
|
|
|
*
|
|
|
|
* TODO: (Scrub C)
|
2019-08-25 04:46:40 +00:00
|
|
|
*/
|
|
|
|
void append_snowflake_vertex_buffer(Gfx *gfx, s32 index, Vec3s vertex1, Vec3s vertex2, Vec3s vertex3) {
|
|
|
|
s32 i = 0;
|
|
|
|
Vtx *vertBuf = (Vtx *) alloc_display_list(15 * sizeof(Vtx));
|
2020-03-02 03:42:52 +00:00
|
|
|
#ifdef VERSION_EU
|
|
|
|
Vtx *p;
|
|
|
|
#endif
|
2019-08-25 04:46:40 +00:00
|
|
|
|
2019-09-01 19:50:50 +00:00
|
|
|
if (vertBuf == NULL) {
|
2019-08-25 04:46:40 +00:00
|
|
|
return;
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
2019-08-25 04:46:40 +00:00
|
|
|
|
|
|
|
for (i = 0; i < 15; i += 3) {
|
|
|
|
vertBuf[i] = gSnowTempVtx[0];
|
2020-03-02 03:42:52 +00:00
|
|
|
#ifdef VERSION_EU
|
|
|
|
p = vertBuf;
|
|
|
|
p += i;
|
|
|
|
p[0].v.ob[0] = gEnvFxBuffer[index + i / 3].xPos + vertex1[0];
|
|
|
|
p[0].v.ob[1] = gEnvFxBuffer[index + i / 3].yPos + vertex1[1];
|
|
|
|
p[0].v.ob[2] = gEnvFxBuffer[index + i / 3].zPos + vertex1[2];
|
|
|
|
#else
|
2019-08-25 04:46:40 +00:00
|
|
|
vertBuf[i].v.ob[0] = gEnvFxBuffer[index + i / 3].xPos + vertex1[0];
|
|
|
|
vertBuf[i].v.ob[1] = gEnvFxBuffer[index + i / 3].yPos + vertex1[1];
|
|
|
|
vertBuf[i].v.ob[2] = gEnvFxBuffer[index + i / 3].zPos + vertex1[2];
|
2020-03-02 03:42:52 +00:00
|
|
|
#endif
|
2019-08-25 04:46:40 +00:00
|
|
|
|
|
|
|
vertBuf[i + 1] = gSnowTempVtx[1];
|
2020-03-02 03:42:52 +00:00
|
|
|
#ifdef VERSION_EU
|
|
|
|
p = vertBuf;
|
|
|
|
p += i;
|
|
|
|
p[1].v.ob[0] = gEnvFxBuffer[index + i / 3].xPos + vertex2[0];
|
|
|
|
p[1].v.ob[1] = gEnvFxBuffer[index + i / 3].yPos + vertex2[1];
|
|
|
|
p[1].v.ob[2] = gEnvFxBuffer[index + i / 3].zPos + vertex2[2];
|
|
|
|
#else
|
2019-08-25 04:46:40 +00:00
|
|
|
vertBuf[i + 1].v.ob[0] = gEnvFxBuffer[index + i / 3].xPos + vertex2[0];
|
|
|
|
vertBuf[i + 1].v.ob[1] = gEnvFxBuffer[index + i / 3].yPos + vertex2[1];
|
|
|
|
vertBuf[i + 1].v.ob[2] = gEnvFxBuffer[index + i / 3].zPos + vertex2[2];
|
2020-03-02 03:42:52 +00:00
|
|
|
#endif
|
2019-08-25 04:46:40 +00:00
|
|
|
|
|
|
|
vertBuf[i + 2] = gSnowTempVtx[2];
|
2020-03-02 03:42:52 +00:00
|
|
|
#ifdef VERSION_EU
|
|
|
|
p = vertBuf;
|
|
|
|
p += i;
|
|
|
|
p[2].v.ob[0] = gEnvFxBuffer[index + i / 3].xPos + vertex3[0];
|
|
|
|
p[2].v.ob[1] = gEnvFxBuffer[index + i / 3].yPos + vertex3[1];
|
|
|
|
p[2].v.ob[2] = gEnvFxBuffer[index + i / 3].zPos + vertex3[2];
|
|
|
|
#else
|
2019-08-25 04:46:40 +00:00
|
|
|
vertBuf[i + 2].v.ob[0] = gEnvFxBuffer[index + i / 3].xPos + vertex3[0];
|
|
|
|
vertBuf[i + 2].v.ob[1] = gEnvFxBuffer[index + i / 3].yPos + vertex3[1];
|
|
|
|
vertBuf[i + 2].v.ob[2] = gEnvFxBuffer[index + i / 3].zPos + vertex3[2];
|
2020-03-02 03:42:52 +00:00
|
|
|
#endif
|
2019-08-25 04:46:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
gSPVertex(gfx, VIRTUAL_TO_PHYSICAL(vertBuf), 15, 0);
|
|
|
|
}
|
|
|
|
|
2019-09-01 19:50:50 +00:00
|
|
|
/**
|
|
|
|
* Updates positions of snow particles and returns a pointer to a display list
|
|
|
|
* drawing all snowflakes.
|
2019-08-25 04:46:40 +00:00
|
|
|
*/
|
|
|
|
Gfx *envfx_update_snow(s32 snowMode, Vec3s marioPos, Vec3s camFrom, Vec3s camTo) {
|
|
|
|
s32 i;
|
|
|
|
s16 radius, pitch, yaw;
|
|
|
|
Vec3s snowCylinderPos;
|
|
|
|
struct SnowFlakeVertex vertex1, vertex2, vertex3;
|
|
|
|
Gfx *gfxStart;
|
|
|
|
Gfx *gfx;
|
|
|
|
|
|
|
|
vertex1 = gSnowFlakeVertex1;
|
|
|
|
vertex2 = gSnowFlakeVertex2;
|
|
|
|
vertex3 = gSnowFlakeVertex3;
|
|
|
|
|
|
|
|
gfxStart = (Gfx *) alloc_display_list((gSnowParticleCount * 6 + 3) * sizeof(Gfx));
|
|
|
|
gfx = gfxStart;
|
|
|
|
|
2019-09-01 19:50:50 +00:00
|
|
|
if (gfxStart == NULL) {
|
2019-08-25 04:46:40 +00:00
|
|
|
return NULL;
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
2019-08-25 04:46:40 +00:00
|
|
|
|
|
|
|
envfx_update_snowflake_count(snowMode, marioPos);
|
|
|
|
|
|
|
|
// Note: to and from are inverted here, so the resulting vector goes towards the camera
|
|
|
|
orbit_from_positions(camTo, camFrom, &radius, &pitch, &yaw);
|
|
|
|
|
|
|
|
switch (snowMode) {
|
|
|
|
case ENVFX_SNOW_NORMAL:
|
|
|
|
// ensure the snow cylinder is no further than 250 units in front
|
|
|
|
// of the camera, and no closer than 1 unit.
|
2019-09-01 19:50:50 +00:00
|
|
|
if (radius > 250) {
|
2019-08-25 04:46:40 +00:00
|
|
|
radius -= 250;
|
2019-09-01 19:50:50 +00:00
|
|
|
} else {
|
2019-08-25 04:46:40 +00:00
|
|
|
radius = 1;
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
2019-08-25 04:46:40 +00:00
|
|
|
|
|
|
|
pos_from_orbit(camTo, snowCylinderPos, radius, pitch, yaw);
|
|
|
|
envfx_update_snow_normal(snowCylinderPos[0], snowCylinderPos[1], snowCylinderPos[2]);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ENVFX_SNOW_WATER:
|
2019-09-01 19:50:50 +00:00
|
|
|
if (radius > 500) {
|
2019-08-25 04:46:40 +00:00
|
|
|
radius -= 500;
|
2019-09-01 19:50:50 +00:00
|
|
|
} else {
|
2019-08-25 04:46:40 +00:00
|
|
|
radius = 1;
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
2019-08-25 04:46:40 +00:00
|
|
|
|
|
|
|
pos_from_orbit(camTo, snowCylinderPos, radius, pitch, yaw);
|
|
|
|
envfx_update_snow_water(snowCylinderPos[0], snowCylinderPos[1], snowCylinderPos[2]);
|
|
|
|
break;
|
|
|
|
case ENVFX_SNOW_BLIZZARD:
|
2019-09-01 19:50:50 +00:00
|
|
|
if (radius > 250) {
|
2019-08-25 04:46:40 +00:00
|
|
|
radius -= 250;
|
2019-09-01 19:50:50 +00:00
|
|
|
} else {
|
2019-08-25 04:46:40 +00:00
|
|
|
radius = 1;
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
2019-08-25 04:46:40 +00:00
|
|
|
|
|
|
|
pos_from_orbit(camTo, snowCylinderPos, radius, pitch, yaw);
|
|
|
|
envfx_update_snow_blizzard(snowCylinderPos[0], snowCylinderPos[1], snowCylinderPos[2]);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
rotate_triangle_vertices((s16 *) &vertex1, (s16 *) &vertex2, (s16 *) &vertex3, pitch, yaw);
|
|
|
|
|
|
|
|
if (snowMode == ENVFX_SNOW_NORMAL || snowMode == ENVFX_SNOW_BLIZZARD) {
|
|
|
|
gSPDisplayList(gfx++, &tiny_bubble_dl_0B006A50); // snowflake with gray edge
|
|
|
|
} else if (snowMode == ENVFX_SNOW_WATER) {
|
|
|
|
gSPDisplayList(gfx++, &tiny_bubble_dl_0B006CD8); // snowflake with blue edge
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < gSnowParticleCount; i += 5) {
|
|
|
|
append_snowflake_vertex_buffer(gfx++, i, (s16 *) &vertex1, (s16 *) &vertex2, (s16 *) &vertex3);
|
|
|
|
|
|
|
|
gSP1Triangle(gfx++, 0, 1, 2, 0);
|
|
|
|
gSP1Triangle(gfx++, 3, 4, 5, 0);
|
|
|
|
gSP1Triangle(gfx++, 6, 7, 8, 0);
|
|
|
|
gSP1Triangle(gfx++, 9, 10, 11, 0);
|
|
|
|
gSP1Triangle(gfx++, 12, 13, 14, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
gSPDisplayList(gfx++, &tiny_bubble_dl_0B006AB0) gSPEndDisplayList(gfx++);
|
|
|
|
|
|
|
|
return gfxStart;
|
|
|
|
}
|
|
|
|
|
2019-09-01 19:50:50 +00:00
|
|
|
/**
|
|
|
|
* Updates the environment effects (snow, flowers, bubbles)
|
|
|
|
* and returns a display list drawing them.
|
2019-08-25 04:46:40 +00:00
|
|
|
*/
|
|
|
|
Gfx *envfx_update_particles(s32 mode, Vec3s marioPos, Vec3s camTo, Vec3s camFrom) {
|
|
|
|
Gfx *gfx;
|
|
|
|
|
2019-09-01 19:50:50 +00:00
|
|
|
if (get_dialog_id() != -1) {
|
2019-08-25 04:46:40 +00:00
|
|
|
return NULL;
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
2019-08-25 04:46:40 +00:00
|
|
|
|
2019-09-01 19:50:50 +00:00
|
|
|
if (gEnvFxMode != 0 && mode != gEnvFxMode) {
|
2019-08-25 04:46:40 +00:00
|
|
|
mode = 0;
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
2019-08-25 04:46:40 +00:00
|
|
|
|
|
|
|
if (mode >= ENVFX_BUBBLE_START) {
|
|
|
|
gfx = envfx_update_bubbles(mode, marioPos, camTo, camFrom);
|
|
|
|
return gfx;
|
|
|
|
}
|
|
|
|
|
2019-09-01 19:50:50 +00:00
|
|
|
if (gEnvFxMode == 0 && envfx_init_snow(mode) == 0) {
|
2019-08-25 04:46:40 +00:00
|
|
|
return NULL;
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
2019-08-25 04:46:40 +00:00
|
|
|
|
|
|
|
switch (mode) {
|
|
|
|
case ENVFX_MODE_NONE:
|
|
|
|
envfx_cleanup_snow(gEnvFxBuffer);
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
case ENVFX_SNOW_NORMAL:
|
|
|
|
gfx = envfx_update_snow(1, marioPos, camFrom, camTo);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ENVFX_SNOW_WATER:
|
|
|
|
gfx = envfx_update_snow(2, marioPos, camFrom, camTo);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ENVFX_SNOW_BLIZZARD:
|
|
|
|
gfx = envfx_update_snow(3, marioPos, camFrom, camTo);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return gfx;
|
|
|
|
}
|