#include <ultra64.h>
#include "sm64.h"
#include "dialog_ids.h"
#include "game_init.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"
struct SnowFlakeVertex {
s16 x;
s16 y;
s16 z;
};
struct EnvFxParticle *gEnvFxBuffer;
Vec3i gSnowCylinderLastPos;
s16 gSnowParticleCount;
s16 gSnowParticleMaxCount;
s8 gEnvFxMode = 0;
UNUSED s32 D_80330644 = 0;
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 } } } };
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;
static struct {
Gfx *pos;
Vtx vertices[15];
} sPrevSnowVertices[140 / 5];
static s16 sPrevSnowParticleCount;
static u32 sPrevSnowTimestamp;
void patch_interpolated_snow_particles(void) {
int i;
if (gGlobalTimer != sPrevSnowTimestamp + 1) {
return;
}
for (i = 0; i < sPrevSnowParticleCount; i += 5) {
gSPVertex(sPrevSnowVertices[i / 5].pos,
VIRTUAL_TO_PHYSICAL(sPrevSnowVertices[i / 5].vertices), 15, 0);
}
}
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;
}
gEnvFxBuffer = mem_pool_alloc(gEffectsMemoryPool, gSnowParticleMaxCount * sizeof(struct EnvFxParticle));
if (!gEnvFxBuffer) {
return 0;
}
bzero(gEnvFxBuffer, gSnowParticleMaxCount * sizeof(struct EnvFxParticle));
gEnvFxMode = mode;
return 1;
}
void envfx_update_snowflake_count(s32 mode, Vec3s marioPos) {
s32 timer = gGlobalTimer;
f32 waterLevel;
switch (mode) {
case ENVFX_SNOW_NORMAL:
if (gSnowParticleMaxCount > gSnowParticleCount) {
if ((timer & 0x3F) == 0) {
gSnowParticleCount += 5;
}
}
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;
if (gSnowParticleCount < 0) {
gSnowParticleCount = 0;
}
if (gSnowParticleCount > gSnowParticleMaxCount) {
gSnowParticleCount = gSnowParticleMaxCount;
}
break;
case ENVFX_SNOW_BLIZZARD:
break;
}
}
void envfx_cleanup_snow(void *snowParticleArray) {
if (gEnvFxMode) {
if (snowParticleArray) {
mem_pool_free(gEffectsMemoryPool, snowParticleArray);
}
gEnvFxMode = ENVFX_MODE_NONE;
}
}
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);
}
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);
}
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;
if (sqr(x - snowCylinderX) + sqr(z - snowCylinderZ) > sqr(300)) {
return 0;
}
if ((y < snowCylinderY - 201) || (snowCylinderY + 201 < y)) {
return 0;
}
return 1;
}
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 * random_float() - 200.0f + snowCylinderX + (s16)(deltaX * 2);
(gEnvFxBuffer + i)->zPos =
400.0f * random_float() - 200.0f + snowCylinderZ + (s16)(deltaZ * 2);
(gEnvFxBuffer + i)->yPos = 200.0f * random_float() + snowCylinderY;
(gEnvFxBuffer + i)->isAlive = 1;
(gEnvFxBuffer + i)->spawnTimestamp = gGlobalTimer;
} else {
(gEnvFxBuffer + i)->xPos += random_float() * 2 - 1.0f + (s16)(deltaX / 1.2);
(gEnvFxBuffer + i)->yPos -= 2 -(s16)(deltaY * 0.8);
(gEnvFxBuffer + i)->zPos += random_float() * 2 - 1.0f + (s16)(deltaZ / 1.2);
}
}
gSnowCylinderLastPos[0] = snowCylinderX;
gSnowCylinderLastPos[1] = snowCylinderY;
gSnowCylinderLastPos[2] = snowCylinderZ;
}
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 * random_float() - 200.0f + snowCylinderX + (s16)(deltaX * 2);
(gEnvFxBuffer + i)->zPos =
400.0f * random_float() - 200.0f + snowCylinderZ + (s16)(deltaZ * 2);
(gEnvFxBuffer + i)->yPos = 400.0f * random_float() - 200.0f + snowCylinderY;
(gEnvFxBuffer + i)->isAlive = 1;
(gEnvFxBuffer + i)->spawnTimestamp = gGlobalTimer;
} else {
(gEnvFxBuffer + i)->xPos += random_float() * 2 - 1.0f + (s16)(deltaX / 1.2) + 20.0f;
(gEnvFxBuffer + i)->yPos -= 5 -(s16)(deltaY * 0.8);
(gEnvFxBuffer + i)->zPos += random_float() * 2 - 1.0f + (s16)(deltaZ / 1.2);
}
}
gSnowCylinderLastPos[0] = snowCylinderX;
gSnowCylinderLastPos[1] = snowCylinderY;
gSnowCylinderLastPos[2] = snowCylinderZ;
}
UNUSED static s32 is_in_mystery_snow_area(s32 x, UNUSED s32 y, s32 z) {
if (sqr(x - 3380) + sqr(z + 520) < sqr(3000)) {
return 1;
}
return 0;
}
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 * random_float() - 200.0f + snowCylinderX;
(gEnvFxBuffer + i)->zPos = 400.0f * random_float() - 200.0f + snowCylinderZ;
(gEnvFxBuffer + i)->yPos = 400.0f * random_float() - 200.0f + snowCylinderY;
(gEnvFxBuffer + i)->isAlive = 1;
(gEnvFxBuffer + i)->spawnTimestamp = gGlobalTimer;
}
}
}
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);
}
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));
Vtx *vertBufInterpolated = (Vtx *) alloc_display_list(15 * sizeof(Vtx));
Vtx *v;
#ifdef VERSION_EU
Vtx *p;
#endif
if (vertBuf == NULL) {
return;
}
for (i = 0; i < 15; i += 3) {
vertBuf[i] = gSnowTempVtx[0];
(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];
vertBuf[i + 1] = gSnowTempVtx[1];
(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];
vertBuf[i + 2] = gSnowTempVtx[2];
(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];
}
for (i = 0; i < 15; i++) {
v = &sPrevSnowVertices[index / 5].vertices[i];
vertBufInterpolated[i] = gSnowTempVtx[i % 3];
if (index < sPrevSnowParticleCount && gGlobalTimer == sPrevSnowTimestamp + 1 &&
gGlobalTimer != gEnvFxBuffer[index + i / 3].spawnTimestamp) {
vertBufInterpolated[i].v.ob[0] = (v->v.ob[0] + vertBuf[i].v.ob[0]) / 2;
vertBufInterpolated[i].v.ob[1] = (v->v.ob[1] + vertBuf[i].v.ob[1]) / 2;
vertBufInterpolated[i].v.ob[2] = (v->v.ob[2] + vertBuf[i].v.ob[2]) / 2;
} else {
vertBufInterpolated[i].v.ob[0] = vertBuf[i].v.ob[0];
vertBufInterpolated[i].v.ob[1] = vertBuf[i].v.ob[1];
vertBufInterpolated[i].v.ob[2] = vertBuf[i].v.ob[2];
}
*v = vertBuf[i];
}
sPrevSnowVertices[index / 5].pos = gfx;
gSPVertex(gfx, VIRTUAL_TO_PHYSICAL(vertBufInterpolated), 15, 0);
}
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;
if (gfxStart == NULL) {
return NULL;
}
envfx_update_snowflake_count(snowMode, marioPos);
orbit_from_positions(camTo, camFrom, &radius, &pitch, &yaw);
switch (snowMode) {
case ENVFX_SNOW_NORMAL:
if (radius > 250) {
radius -= 250;
} else {
radius = 1;
}
pos_from_orbit(camTo, snowCylinderPos, radius, pitch, yaw);
envfx_update_snow_normal(snowCylinderPos[0], snowCylinderPos[1], snowCylinderPos[2]);
break;
case ENVFX_SNOW_WATER:
if (radius > 500) {
radius -= 500;
} else {
radius = 1;
}
pos_from_orbit(camTo, snowCylinderPos, radius, pitch, yaw);
envfx_update_snow_water(snowCylinderPos[0], snowCylinderPos[1], snowCylinderPos[2]);
break;
case ENVFX_SNOW_BLIZZARD:
if (radius > 250) {
radius -= 250;
} else {
radius = 1;
}
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);
} else if (snowMode == ENVFX_SNOW_WATER) {
gSPDisplayList(gfx++, &tiny_bubble_dl_0B006CD8);
}
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);
}
sPrevSnowParticleCount = gSnowParticleCount;
sPrevSnowTimestamp = gGlobalTimer;
gSPDisplayList(gfx++, &tiny_bubble_dl_0B006AB0) gSPEndDisplayList(gfx++);
return gfxStart;
}
Gfx *envfx_update_particles(s32 mode, Vec3s marioPos, Vec3s camTo, Vec3s camFrom) {
Gfx *gfx;
if (get_dialog_id() != DIALOG_NONE) {
return NULL;
}
if (gEnvFxMode != 0 && mode != gEnvFxMode) {
mode = 0;
}
if (mode >= ENVFX_BUBBLE_START) {
gfx = envfx_update_bubbles(mode, marioPos, camTo, camFrom);
return gfx;
}
if (gEnvFxMode == 0 && envfx_init_snow(mode) == 0) {
return NULL;
}
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;
}