diff --git a/src/game/shadow.c b/src/game/shadow.c index 4354a4d2c..8d95c5774 100644 --- a/src/game/shadow.c +++ b/src/game/shadow.c @@ -1,12 +1,13 @@ #include #include -#include #include "engine/math_util.h" #include "engine/surface_collision.h" +#include "behavior_data.h" #include "geo_misc.h" #include "level_table.h" #include "memory.h" +#include "level_update.h" #include "object_list_processor.h" #include "rendering_graph_node.h" #include "segment2.h" @@ -18,140 +19,28 @@ * This file implements a self-contained subsystem used to draw shadows. */ -/** - * Encapsulation of information about a shadow. - */ -struct Shadow { - /* The (x, y, z) position of the object whose shadow this is. */ - f32 parentX; - f32 parentY; - f32 parentZ; - /* The y-position of the floor (or water or lava) underneath the object. */ - f32 floorHeight; - /* Initial (unmodified) size of the shadow. */ - f32 shadowScale; - /* (nx, ny, nz) normal vector of the floor underneath the object. */ - f32 floorNormalX; - f32 floorNormalY; - f32 floorNormalZ; - /* "originOffset" of the floor underneath the object. */ - f32 floorOriginOffset; - /* Angle describing "which way a marble would roll," in degrees. */ - f32 floorDownwardAngle; - /* Angle describing "how tilted the ground is" in degrees (-90 to 90). */ - f32 floorTilt; - /* Initial solidity of the shadow, from 0 to 255 (just an alpha value). */ - u8 solidity; -}; - -/** - * Constant to indicate that a shadow should not be drawn. - * This is used to disable shadows during specific frames of Mario's - * animations. - */ -#define SHADOW_SOLIDITY_NO_SHADOW 0 -/** - * Constant to indicate that a shadow's solidity has been pre-set by a previous - * function and should not be overwritten. - */ -#define SHADOW_SOILDITY_ALREADY_SET 1 -/** - * Constant to indicate that a shadow's solidity has not yet been set. - */ -#define SHADOW_SOLIDITY_NOT_YET_SET 2 - -/** - * Constant to indicate any sort of circular shadow. - */ -#define SHADOW_SHAPE_CIRCLE 10 -/** - * Constant to indicate any sort of rectangular shadow. - */ -#define SHADOW_SHAPE_SQUARE 20 - -/** - * Constant to indicate a shadow consists of 9 vertices. - */ -#define SHADOW_WITH_9_VERTS 0 -/** - * Constant to indicate a shadow consists of 4 vertices. - */ -#define SHADOW_WITH_4_VERTS 1 - -/** - * A struct containing info about hardcoded rectangle shadows. - */ -typedef struct { - /* Half the width of the rectangle. */ - f32 halfWidth; - /* Half the length of the rectangle. */ - f32 halfLength; - /* Flag for if this shadow be smaller when its object is further away. */ - s8 scaleWithDistance; -} shadowRectangle; - /** * An array consisting of all the hardcoded rectangle shadows in the game. */ -shadowRectangle rectangles[2] = { - /* Shadow for Spindels. */ - { 360.0f, 230.0f, TRUE }, - /* Shadow for Whomps. */ - { 200.0f, 180.0f, TRUE } +static ShadowRectangle sShadowRectangles[2] = { + { 7.2f, 4.6f, TRUE }, // Spindel + { 4.0f, 3.6f, TRUE }, // Whomp }; -// See shadow.h for documentation. -s8 gShadowFlags; -s16 sSurfaceTypeBelowShadow; - -/** - * Let (oldZ, oldX) be the relative coordinates of a point on a rectangle, - * assumed to be centered at the origin on the standard SM64 X-Z plane. This - * function will update (newZ, newX) to equal the new coordinates of that point - * after a rotation equal to the yaw of the current graph node object. - */ -void rotate_rectangle(f32 *newZ, f32 *newX, f32 oldZ, f32 oldX) { - struct Object *obj = (struct Object *) gCurGraphNodeObject; - f32 s = sins(obj->oFaceAngleYaw); - f32 c = coss(obj->oFaceAngleYaw); - *newZ = oldZ * c - oldX * s; - *newX = oldZ * s + oldX * c; -} - -/** - * Return atan2(a, b) in degrees. Note that the argument order is swapped from - * the standard atan2. - */ -f32 atan2_deg(f32 a, f32 b) { - return ((f32) atan2s(a, b) / 65535.0 * 360.0); -} +struct Shadow gCurrShadow; +struct Shadow *s = &gCurrShadow; /** * Shrink a shadow when its parent object is further from the floor, given the * initial size of the shadow and the current distance. */ f32 scale_shadow_with_distance(f32 initial, f32 distFromFloor) { - f32 newScale; - if (distFromFloor <= 0.0f) { - newScale = initial; + return initial; } else if (distFromFloor >= 600.0f) { - newScale = initial * 0.5f; + return initial * 0.5f; } else { - newScale = initial * (1.0f - (0.5f * distFromFloor / 600.0f)); - } - - return newScale; -} - -/** - * Disable a shadow when its parent object is more than 600 units from the ground. - */ -f32 disable_shadow_with_distance(f32 shadowScale, f32 distFromFloor) { - if (distFromFloor >= 600.0f) { - return 0.0f; - } else { - return shadowScale; + return initial * (1.0f - ((distFromFloor * 0.5f) / 600.0f)); } } @@ -159,8 +48,6 @@ f32 disable_shadow_with_distance(f32 shadowScale, f32 distFromFloor) { * Dim a shadow when its parent object is further from the ground. */ s32 dim_shadow_with_distance(u8 solidity, f32 distFromFloor) { - f32 ret; - if (solidity < 121) { return solidity; } else if (distFromFloor <= 0.0f) { @@ -168,8 +55,7 @@ s32 dim_shadow_with_distance(u8 solidity, f32 distFromFloor) { } else if (distFromFloor >= 600.0f) { return 120; } else { - ret = ((120 - solidity) * distFromFloor) / 600.0f + (f32) solidity; - return ret; + return (((120 - solidity) * distFromFloor) / 600.0f) + (f32) solidity; } } @@ -177,313 +63,86 @@ s32 dim_shadow_with_distance(u8 solidity, f32 distFromFloor) { * Return the water level below a shadow, or 0 if the water level is below * -10,000. */ -f32 get_water_level_below_shadow(struct Shadow *s, struct Surface **waterFloor) { - f32 waterLevel = find_water_level_and_floor(s->parentX, s->parentZ, waterFloor); - if (waterLevel < FLOOR_LOWER_LIMIT_SHADOW) { - return 0; - } else if (s->parentY >= waterLevel && s->floorHeight <= waterLevel) { - gShadowFlags |= SHADOW_FLAG_WATER_BOX; - return waterLevel; +f32 get_water_level_below_shadow(Vec3f pos, struct Surface **waterFloor) { + f32 waterLevel = find_water_level_and_floor(pos[0], pos[2], waterFloor); + + if (waterLevel < FLOOR_LOWER_LIMIT_MISC) { + return FLOOR_LOWER_LIMIT_MISC; + } else if (pos[1] >= waterLevel && s->floorHeight <= waterLevel) { + s->flags |= SHADOW_FLAG_WATER_BOX; } + return waterLevel; } /** * Initialize a shadow. Return 0 on success, 1 on failure. * - * @param xPos,yPos,zPos Position of the parent object (not the shadow) + * @param pos Position of the parent object (not the shadow) * @param shadowScale Diameter of the shadow * @param overwriteSolidity Flag for whether the existing shadow solidity should * be dimmed based on its distance to the floor */ -s32 init_shadow(struct Shadow *s, f32 xPos, f32 yPos, f32 zPos, s16 shadowScale, u8 overwriteSolidity) { - struct Surface *floor; +s32 init_shadow(Vec3f pos, s16 shadowScale, s8 shadowType, u8 overwriteSolidity) { + f32 baseScale; struct Surface *waterFloor = NULL; - - s->parentX = xPos; - s->parentY = yPos; - s->parentZ = zPos; - - s->floorHeight = find_floor(s->parentX, s->parentY, s->parentZ, &floor); - - f32 waterLevel = get_water_level_below_shadow(s, &waterFloor); - + // Check for water under the shadow. + f32 waterLevel = get_water_level_below_shadow(pos, &waterFloor); // if (gEnvironmentRegions != 0) { // waterLevel = get_water_level_below_shadow(s); // } - if (gShadowFlags & SHADOW_FLAG_WATER_BOX) { + if (s->flags & SHADOW_FLAG_WATER_BOX) { + // If there is water under the shadow, put the shadow on the water. s->floorHeight = waterLevel; if (waterFloor != NULL) { - s->floorNormalX = waterFloor->normal.x; - s->floorNormalY = waterFloor->normal.y; - s->floorNormalZ = waterFloor->normal.z; - s->floorOriginOffset = waterFloor->originOffset; - gShadowFlags &= ~SHADOW_FLAG_WATER_BOX; - gShadowFlags |= SHADOW_FLAG_WATER_SURFACE; + // If the water is a surface: + s->floor = waterFloor; + s->flags &= ~SHADOW_FLAG_WATER_BOX; + s->flags |= SHADOW_FLAG_WATER_SURFACE; s->solidity = 200; } else { - gShadowFlags &= ~SHADOW_FLAG_WATER_SURFACE; - // Assume that the water is flat. - s->floorNormalX = 0.0f; - s->floorNormalY = 1.0f; - s->floorNormalZ = 0.0f; - s->floorOriginOffset = -waterLevel; + // If the water is an environment box: + s->flags &= ~SHADOW_FLAG_WATER_SURFACE; + // Assume that the water is flat, so the normal vector points up. + s->floorNormal[0] = 0.0f; + ((u32 *) s->floorNormal)[1] = FLOAT_ONE; + s->floorNormal[2] = 0.0f; } } else { // Don't draw a shadow if the floor is lower than expected possible, // or if the y-normal is negative (an unexpected result). - if (s->floorHeight < FLOOR_LOWER_LIMIT_SHADOW || floor->normal.y <= 0.0f) { - return 1; - } - - s->floorNormalX = floor->normal.x; - s->floorNormalY = floor->normal.y; - s->floorNormalZ = floor->normal.z; - s->floorOriginOffset = floor->originOffset; - } - - if (overwriteSolidity) { - s->solidity = dim_shadow_with_distance(overwriteSolidity, yPos - s->floorHeight); - } - - s->shadowScale = scale_shadow_with_distance(shadowScale, yPos - s->floorHeight); - - s->floorDownwardAngle = atan2_deg(s->floorNormalZ, s->floorNormalX); - - f32 floorSteepness = (sqr(s->floorNormalX) + sqr(s->floorNormalZ)); - - // This if-statement avoids dividing by 0. - if (floorSteepness == 0.0f) { - s->floorTilt = 0; - } else { - floorSteepness = sqrtf(floorSteepness); - s->floorTilt = 90.0f - atan2_deg(floorSteepness, s->floorNormalY); - } - return 0; -} - -/** - * Given a `vertexNum` from a shadow with nine vertices, update the - * texture coordinates corresponding to that vertex. That is: - * 0 = (-15, -15) 1 = (0, -15) 2 = (15, -15) - * 3 = (-15, 0) 4 = (0, 0) 5 = (15, 0) - * 6 = (-15, 15) 7 = (0, 15) 8 = (15, 15) - */ -void get_texture_coords_9_vertices(s8 vertexNum, s16 *textureX, s16 *textureY) { - *textureX = vertexNum % 3 * 15 - 15; - *textureY = vertexNum / 3 * 15 - 15; -} - -/** - * Given a `vertexNum` from a shadow with four vertices, update the - * texture coordinates corresponding to that vertex. That is: - * 0 = (-15, -15) 1 = (15, -15) - * 2 = (-15, 15) 3 = (15, 15) - */ -void get_texture_coords_4_vertices(s8 vertexNum, s16 *textureX, s16 *textureY) { - *textureX = (vertexNum % 2) * 2 * 15 - 15; - *textureY = (vertexNum / 2) * 2 * 15 - 15; -} - -/** - * Make a shadow's vertex at a position relative to its parent. - * - * @param vertices A preallocated display list for vertices - * @param index Index into `vertices` to insert the vertex - * @param relX,relY,relZ Vertex position relative to its parent object - * @param alpha Opacity of the vertex - * @param shadowVertexType One of SHADOW_WITH_9_VERTS or SHADOW_WITH_4_VERTS - */ -void make_shadow_vertex_at_xyz(Vtx *vertices, s8 index, f32 relX, f32 relY, f32 relZ, u8 alpha, s8 shadowVertexType) { - s16 vtxX = round_float(relX); - s16 vtxY = round_float(relY); - s16 vtxZ = round_float(relZ); - s16 textureX = 0; - s16 textureY = 0; - - switch (shadowVertexType) { - case SHADOW_WITH_9_VERTS: - get_texture_coords_9_vertices(index, &textureX, &textureY); - break; - case SHADOW_WITH_4_VERTS: - get_texture_coords_4_vertices(index, &textureX, &textureY); - break; - } - - // Move the shadow up and over slightly while standing on a flying carpet. - if (gShadowFlags & SHADOW_FLAG_RAISED) { - vtxX += 5; - vtxY += 5; - vtxZ += 5; - } - make_vertex(vertices, index, vtxX, vtxY, vtxZ, textureX << 5, textureY << 5, 255, 255, 255, alpha); // shadows are black -} - -/** - * Given an (x, z)-position close to a shadow, extrapolate the y-position - * according to the floor's normal vector. - */ -f32 extrapolate_vertex_y_position(struct Shadow s, f32 vtxX, f32 vtxZ) { - return -(s.floorNormalX * vtxX + s.floorNormalZ * vtxZ + s.floorOriginOffset) / s.floorNormalY; -} - -/** - * Given a shadow vertex with the given `index`, return the corresponding texture - * coordinates ranging in the square with corners at (-1, -1), (1, -1), (-1, 1), - * and (1, 1) in the x-z plane. See `get_texture_coords_9_vertices()` and - * `get_texture_coords_4_vertices()`, which have similar functionality, but - * return 15 times these values. - */ -void get_vertex_coords(s8 index, s8 shadowVertexType, s8 *xCoord, s8 *zCoord) { - *xCoord = index % (3 - shadowVertexType) - 1; - *zCoord = index / (3 - shadowVertexType) - 1; - - // This just corrects the 4-vertex case to have consistent results with the 9-vertex case. - if (shadowVertexType == SHADOW_WITH_4_VERTS) { - if (*xCoord == 0) *xCoord = 1; - if (*zCoord == 0) *zCoord = 1; - } -} - -/** - * Populate `xPosVtx`, `yPosVtx`, and `zPosVtx` with the (x, y, z) position of the - * shadow vertex with the given index. If the shadow is to have 9 vertices, - * then each of those vertices is clamped down to the floor below it. Otherwise, - * in the 4 vertex case, the vertex positions are extrapolated from the center - * of the shadow. - * - * In practice, due to the if-statement in `make_shadow_vertex()`, the 9 - * vertex and 4 vertex cases are identical, and the above-described clamping - * behavior is overwritten. - */ -void calculate_vertex_xyz(s8 index, struct Shadow s, f32 *xPosVtx, f32 *yPosVtx, f32 *zPosVtx, s8 shadowVertexType) { - f32 tiltedScale = cosf(s.floorTilt * M_PI / 180.0) * s.shadowScale; - f32 downwardAngle = s.floorDownwardAngle * M_PI / 180.0; - s8 xCoordUnit, zCoordUnit; - - // This makes xCoordUnit and yCoordUnit each one of -1, 0, or 1. - get_vertex_coords(index, shadowVertexType, &xCoordUnit, &zCoordUnit); - - f32 halfScale = (xCoordUnit * s.shadowScale) / 2.0f; - f32 halfTiltedScale = (zCoordUnit * tiltedScale) / 2.0f; - - *xPosVtx = (halfTiltedScale * sinf(downwardAngle)) + (halfScale * cosf(downwardAngle)) + s.parentX; - *zPosVtx = (halfTiltedScale * cosf(downwardAngle)) - (halfScale * sinf(downwardAngle)) + s.parentZ; - - if (gShadowFlags & SHADOW_FLAG_WATER_BOX) { - *yPosVtx = s.floorHeight; - } else { - switch (shadowVertexType) { - /** - * Note that this dichotomy is later overwritten in - * make_shadow_vertex(). - */ - case SHADOW_WITH_9_VERTS: - // Clamp this vertex's y-position to that of the floor directly - // below it, which may differ from the floor below the center - // vertex. - *yPosVtx = find_floor_height(*xPosVtx, s.parentY, *zPosVtx); - break; - case SHADOW_WITH_4_VERTS: - // Do not clamp. Instead, extrapolate the y-position of this - // vertex based on the directly floor below the parent object. - *yPosVtx = extrapolate_vertex_y_position(s, *xPosVtx, *zPosVtx); - break; + if (s->floorNormal[1] <= 0.0f || s->floorHeight < FLOOR_LOWER_LIMIT_MISC) { + return TRUE; } } -} -/** - * Given a vertex's location, return the dot product of the - * position of that vertex (relative to the shadow's center) with the floor - * normal (at the shadow's center). - * - * Since it is a dot product, this returns 0 if these two vectors are - * perpendicular, meaning the ground is locally flat. It returns nonzero - * in most cases where `vtxY` is on a different floor triangle from the - * center vertex, as in the case with SHADOW_WITH_9_VERTS, which sets - * the y-value from `find_floor`. (See the bottom of `calculate_vertex_xyz`.) - */ -s16 floor_local_tilt(struct Shadow s, f32 vtxX, f32 vtxY, f32 vtxZ) { - f32 relX = vtxX - s.parentX; - f32 relY = vtxY - s.floorHeight; - f32 relZ = vtxZ - s.parentZ; + if (shadowType != SHADOW_SQUARE_PERMANENT) { + // Set solidity and scale based on distance. + f32 dy = (pos[1] - s->floorHeight); - return ((relX * s.floorNormalX) + (relY * s.floorNormalY) + (relZ * s.floorNormalZ)); -} + if (overwriteSolidity) { + s->solidity = dim_shadow_with_distance(overwriteSolidity, dy); + } -/** - * Make a particular vertex from a shadow, calculating its position and solidity. - */ -void make_shadow_vertex(Vtx *vertices, s8 index, struct Shadow s, s8 shadowVertexType) { - f32 xPosVtx, yPosVtx, zPosVtx; - f32 relX, relY, relZ; - - u8 solidity = s.solidity; - if (gShadowFlags & SHADOW_FLAG_WATER_BOX) { - solidity = 200; + baseScale = scale_shadow_with_distance(shadowScale, dy); + } else { + s->solidity = overwriteSolidity; + baseScale = shadowScale; } + vec3f_set(s->scale, baseScale, baseScale, baseScale); - calculate_vertex_xyz(index, s, &xPosVtx, &yPosVtx, &zPosVtx, shadowVertexType); - - /** - * This is the hack that makes "SHADOW_WITH_9_VERTS" act identically to - * "SHADOW_WITH_4_VERTS" in the game; this same hack is disabled by the - * GameShark code in this video: https://youtu.be/MSIh4rtNGF0. The code in - * the video makes `extrapolate_vertex_y_position` return the same value as - * the last-called function that returns a float; in this case, that's - * `find_floor`, which this if-statement was designed to overwrite in the - * first place. Thus, this if-statement is disabled by that code. - * - * The last condition here means the y-position calculated previously - * was probably on a different floor triangle from the center vertex. - * The gShadowAboveWaterOrLava check is redundant, since `floor_local_tilt` - * will always be 0 over water or lava (since they are always flat). - */ - if (shadowVertexType == SHADOW_WITH_9_VERTS && !(gShadowFlags & SHADOW_FLAG_WATER_BOX) && floor_local_tilt(s, xPosVtx, yPosVtx, zPosVtx) != 0) { - yPosVtx = extrapolate_vertex_y_position(s, xPosVtx, zPosVtx); - solidity = 0; - } - relX = xPosVtx - s.parentX; - relY = yPosVtx - s.parentY; - relZ = zPosVtx - s.parentZ; - - make_shadow_vertex_at_xyz(vertices, index, relX, relY, relZ, solidity, shadowVertexType); -} - -/** - * Add a shadow to the given display list. - */ -void add_shadow_to_display_list(Gfx *displayListHead, Vtx *verts, s8 shadowVertexType, s8 shadowShape) { - switch (shadowShape) { - case SHADOW_SHAPE_CIRCLE: - gSPDisplayList(displayListHead++, dl_shadow_circle); - break; - case SHADOW_SHAPE_SQUARE: - gSPDisplayList(displayListHead++, dl_shadow_square) break; - } - switch (shadowVertexType) { - case SHADOW_WITH_9_VERTS: - gSPVertex(displayListHead++, verts, 9, 0); - gSPDisplayList(displayListHead++, dl_shadow_9_verts); - break; - case SHADOW_WITH_4_VERTS: - gSPVertex(displayListHead++, verts, 4, 0); - gSPDisplayList(displayListHead++, dl_shadow_4_verts); - break; - } - gSPDisplayList(displayListHead++, dl_shadow_end); - gSPEndDisplayList(displayListHead); + return !(s->solidity); } /** * Linearly interpolate a shadow's solidity between zero and finalSolidity * depending on curr's relation to start and end. */ -void linearly_interpolate_solidity_positive(struct Shadow *s, u8 finalSolidity, s16 curr, s16 start, s16 end) { +void linearly_interpolate_solidity_positive(u8 finalSolidity, s16 curr, s16 start, + s16 end) { if (curr >= 0 && curr < start) { s->solidity = 0; } else if (end < curr) { @@ -498,7 +157,8 @@ void linearly_interpolate_solidity_positive(struct Shadow *s, u8 finalSolidity, * depending on curr's relation to start and end. Note that if curr < start, * the solidity will be zero. */ -void linearly_interpolate_solidity_negative(struct Shadow *s, u8 initialSolidity, s16 curr, s16 start, s16 end) { +void linearly_interpolate_solidity_negative(u8 initialSolidity, s16 curr, s16 start, + s16 end) { // The curr < start case is not handled. Thus, if start != 0, this function // will have the surprising behavior of hiding the shadow until start. // This is not necessarily a bug, since this function is only used once, @@ -513,332 +173,176 @@ void linearly_interpolate_solidity_negative(struct Shadow *s, u8 initialSolidity /** * Change a shadow's solidity based on the player's current animation frame. */ -s32 correct_shadow_solidity_for_animations(s32 isLuigi, u8 initialSolidity, struct Shadow *shadow) { - struct Object *player; - s8 ret; - s16 animFrame; - - if (isLuigi) { - /** - * This is evidence of a removed second player, likely Luigi. - * This variable lies in memory just after the gMarioObject and - * has the same type of shadow that Mario does. The `isLuigi` - * variable is never 1 in the game. Note that since this was a - * switch-case, not an if-statement, the programmers possibly - * intended there to be even more than 2 characters. - */ - player = gLuigiObject; - } else { - player = gMarioObject; - } - - animFrame = player->header.gfx.animInfo.animFrame; - switch (player->header.gfx.animInfo.animID) { +s32 correct_shadow_solidity_for_animations(u8 initialSolidity) { + s16 animFrame = gMarioObject->header.gfx.animInfo.animFrame; + switch (gMarioObject->header.gfx.animInfo.animID) { case MARIO_ANIM_IDLE_ON_LEDGE: - ret = SHADOW_SOLIDITY_NO_SHADOW; - break; + return SHADOW_SOLIDITY_NO_SHADOW; case MARIO_ANIM_FAST_LEDGE_GRAB: - linearly_interpolate_solidity_positive(shadow, initialSolidity, animFrame, 5, 14); - ret = SHADOW_SOILDITY_ALREADY_SET; - break; + linearly_interpolate_solidity_positive(initialSolidity, animFrame, 5, 14); + return SHADOW_SOILDITY_ALREADY_SET; case MARIO_ANIM_SLOW_LEDGE_GRAB: - linearly_interpolate_solidity_positive(shadow, initialSolidity, animFrame, 21, 33); - ret = SHADOW_SOILDITY_ALREADY_SET; - break; + linearly_interpolate_solidity_positive(initialSolidity, animFrame, 21, 33); + return SHADOW_SOILDITY_ALREADY_SET; case MARIO_ANIM_CLIMB_DOWN_LEDGE: - linearly_interpolate_solidity_negative(shadow, initialSolidity, animFrame, 0, 5); - ret = SHADOW_SOILDITY_ALREADY_SET; - break; + linearly_interpolate_solidity_negative(initialSolidity, animFrame, 0, 5); + return SHADOW_SOILDITY_ALREADY_SET; default: - ret = SHADOW_SOLIDITY_NOT_YET_SET; - break; + return SHADOW_SOLIDITY_NOT_YET_SET; } - return ret; } /** * Slightly change the height of a shadow in levels with lava. */ -void correct_lava_shadow_height(struct Shadow *s) { - if (gCurrLevelNum == LEVEL_BITFS && sSurfaceTypeBelowShadow == SURFACE_BURNING) { - if (s->floorHeight < -3000.0f) { - s->floorHeight = -3062.0f; - gShadowFlags |= SHADOW_FLAG_WATER_BOX; - } else if (s->floorHeight > 3400.0f) { - s->floorHeight = 3492.0f; - gShadowFlags |= SHADOW_FLAG_WATER_BOX; - } - } else if (gCurrLevelNum == LEVEL_LLL && gCurrAreaIndex == 1 && sSurfaceTypeBelowShadow == SURFACE_BURNING) { - s->floorHeight = 5.0f; - gShadowFlags |= SHADOW_FLAG_WATER_BOX; - } -} - -/** - * Create a shadow under a player, correcting that shadow's opacity during - * appropriate animations and other states. - */ -Gfx *create_shadow_player(f32 xPos, f32 yPos, f32 zPos, s16 shadowScale, u8 solidity, s32 isLuigi) { - struct Shadow shadow; - s8 ret = 0; - s32 i; - - // Update global variables about whether Mario is on a flying carpet. - if (gCurrLevelNum == LEVEL_RR && sSurfaceTypeBelowShadow != SURFACE_DEATH_PLANE) { - switch (gFlyingCarpetState) { - case FLYING_CARPET_MOVING_WITHOUT_MARIO: - gShadowFlags |= (SHADOW_FLAG_ICE_CARPET | SHADOW_FLAG_RAISED); - break; - case FLYING_CARPET_MOVING_WITH_MARIO: - gShadowFlags = SHADOW_FLAG_ICE_CARPET; - break; +void correct_lava_shadow_height(void) { +#ifdef ENABLE_VANILLA_LEVEL_SPECIFIC_CHECKS + if (s->floor->type == SURFACE_BURNING) { + if (gCurrLevelNum == LEVEL_BITFS) { + if (s->floorHeight < -3000.0f) { + s->floorHeight = -3062.0f; + s->flags |= SHADOW_FLAG_WATER_BOX; + } else if (s->floorHeight > 3400.0f) { + s->floorHeight = 3492.0f; + s->flags |= SHADOW_FLAG_WATER_BOX; + } + } else if (gCurrLevelNum == LEVEL_LLL + && gCurrAreaIndex == 1) { + s->floorHeight = 5.0f; + s->flags |= SHADOW_FLAG_WATER_BOX; } } - - switch (correct_shadow_solidity_for_animations(isLuigi, solidity, &shadow)) { - case SHADOW_SOLIDITY_NO_SHADOW: return NULL; break; - case SHADOW_SOILDITY_ALREADY_SET: - ret = init_shadow(&shadow, xPos, yPos, zPos, shadowScale, /* overwriteSolidity */ 0); - break; - case SHADOW_SOLIDITY_NOT_YET_SET: - ret = init_shadow(&shadow, xPos, yPos, zPos, shadowScale, solidity); - break; - } - if (ret != 0) { - return NULL; - } - - Vtx *verts = alloc_display_list(9 * sizeof(Vtx)); - Gfx *displayList = alloc_display_list(5 * sizeof(Gfx)); - if (verts == NULL || displayList == NULL) { - return NULL; - } - - correct_lava_shadow_height(&shadow); - - for (i = 0; i < 9; i++) { - make_shadow_vertex(verts, i, shadow, SHADOW_WITH_9_VERTS); - } - add_shadow_to_display_list(displayList, verts, SHADOW_WITH_9_VERTS, SHADOW_SHAPE_CIRCLE); - return displayList; +#endif } /** - * Create a circular shadow composed of 9 vertices. + * Add a shadow to the given display list. + * shadowType 0 uses a circle texture, the rest use a square texture. + * Uses environment alpha for shadow solidity. */ -Gfx *create_shadow_circle_9_verts(f32 xPos, f32 yPos, f32 zPos, s16 shadowScale, u8 solidity) { - struct Shadow shadow; - s32 i; - - if (init_shadow(&shadow, xPos, yPos, zPos, shadowScale, solidity) != 0) { - return NULL; - } - - Vtx *verts = alloc_display_list(9 * sizeof(Vtx)); - Gfx *displayList = alloc_display_list(5 * sizeof(Gfx)); - - if (verts == NULL || displayList == NULL) { - return 0; - } - for (i = 0; i < 9; i++) { - make_shadow_vertex(verts, i, shadow, SHADOW_WITH_9_VERTS); - } - add_shadow_to_display_list(displayList, verts, SHADOW_WITH_9_VERTS, SHADOW_SHAPE_CIRCLE); - return displayList; -} - -/** - * Create a circular shadow composed of 4 vertices. - */ -Gfx *create_shadow_circle_4_verts(f32 xPos, f32 yPos, f32 zPos, s16 shadowScale, u8 solidity) { - struct Shadow shadow; - s32 i; - - if (init_shadow(&shadow, xPos, yPos, zPos, shadowScale, solidity) != 0) { - return NULL; - } - - Vtx *verts = alloc_display_list(4 * sizeof(Vtx)); - Gfx *displayList = alloc_display_list(5 * sizeof(Gfx)); - - if (verts == NULL || displayList == NULL) { - return 0; - } - - for (i = 0; i < 4; i++) { - make_shadow_vertex(verts, i, shadow, SHADOW_WITH_4_VERTS); - } - add_shadow_to_display_list(displayList, verts, SHADOW_WITH_4_VERTS, SHADOW_SHAPE_CIRCLE); - return displayList; -} - -/** - * Create a circular shadow composed of 4 vertices and assume that the ground - * underneath it is totally flat. - */ -Gfx *create_shadow_circle_assuming_flat_ground(f32 xPos, f32 yPos, f32 zPos, s16 shadowScale, u8 solidity) { - f32 distBelowFloor; - f32 floorHeight; - f32 radius = shadowScale / 2; - - if (gCurGraphNodeObjectNode->oFloor != NULL) { - floorHeight = gCurGraphNodeObjectNode->oFloorHeight; - } else { - floorHeight = find_floor_height(xPos, yPos, zPos); - } - - if (floorHeight < FLOOR_LOWER_LIMIT_SHADOW) { - return NULL; - } else { - distBelowFloor = floorHeight - yPos; - } - - Vtx *verts = alloc_display_list(4 * sizeof(Vtx)); - Gfx *displayList = alloc_display_list(5 * sizeof(Gfx)); - - if (verts == NULL || displayList == NULL) { - return 0; - } - - make_shadow_vertex_at_xyz(verts, 0, -radius, distBelowFloor, -radius, solidity, 1); - make_shadow_vertex_at_xyz(verts, 1, radius, distBelowFloor, -radius, solidity, 1); - make_shadow_vertex_at_xyz(verts, 2, -radius, distBelowFloor, radius, solidity, 1); - make_shadow_vertex_at_xyz(verts, 3, radius, distBelowFloor, radius, solidity, 1); - - add_shadow_to_display_list(displayList, verts, SHADOW_WITH_4_VERTS, SHADOW_SHAPE_CIRCLE); - return displayList; -} - -/** - * Create a rectangular shadow composed of 4 vertices. This assumes the ground - * underneath the shadow is totally flat. - */ -Gfx *create_shadow_rectangle(f32 halfWidth, f32 halfLength, f32 relY, u8 solidity) { - Vtx *verts = alloc_display_list(4 * sizeof(Vtx)); - Gfx *displayList = alloc_display_list(5 * sizeof(Gfx)); - f32 frontLeftX, frontLeftZ, frontRightX, frontRightZ, backLeftX, backLeftZ, backRightX, backRightZ; - - if (verts == NULL || displayList == NULL) { - return NULL; - } - - // Rotate the shadow based on the parent object's face angle. - rotate_rectangle(&frontLeftZ, &frontLeftX, -halfLength, -halfWidth); - rotate_rectangle(&frontRightZ, &frontRightX, -halfLength, halfWidth); - rotate_rectangle(&backLeftZ, &backLeftX, halfLength, -halfWidth); - rotate_rectangle(&backRightZ, &backRightX, halfLength, halfWidth); - - make_shadow_vertex_at_xyz(verts, 0, frontLeftX, relY, frontLeftZ, solidity, 1); - make_shadow_vertex_at_xyz(verts, 1, frontRightX, relY, frontRightZ, solidity, 1); - make_shadow_vertex_at_xyz(verts, 2, backLeftX, relY, backLeftZ, solidity, 1); - make_shadow_vertex_at_xyz(verts, 3, backRightX, relY, backRightZ, solidity, 1); - - add_shadow_to_display_list(displayList, verts, SHADOW_WITH_4_VERTS, SHADOW_SHAPE_SQUARE); - return displayList; -} - -/** - * Populate `shadowHeight` and `solidity` appropriately; the default solidity - * value is 200. Return 0 if a shadow should be drawn, 1 if not. - */ -s32 get_shadow_height_solidity(f32 xPos, f32 yPos, f32 zPos, f32 *shadowHeight, u8 *solidity) { - if (gCurGraphNodeObjectNode->oFloor != NULL) { - *shadowHeight = gCurGraphNodeObjectNode->oFloorHeight; - } else { - *shadowHeight = find_floor_height(xPos, yPos, zPos); - } - - if (*shadowHeight < FLOOR_LOWER_LIMIT_SHADOW) { - return TRUE; - } else { - f32 waterLevel = find_water_level(xPos, zPos); - if (waterLevel < FLOOR_LOWER_LIMIT_SHADOW) { - // Dead if-statement. There may have been an assert here. - } else if (yPos >= waterLevel && waterLevel >= *shadowHeight) { - gShadowFlags |= SHADOW_FLAG_WATER_BOX; - *shadowHeight = waterLevel; - *solidity = 200; - } - } - return FALSE; -} - -/** - * Create a square shadow composed of 4 vertices. - */ -Gfx *create_shadow_square(f32 xPos, f32 yPos, f32 zPos, s16 shadowScale, u8 solidity, s8 shadowType) { - f32 shadowHeight; - f32 shadowRadius; - - if (get_shadow_height_solidity(xPos, yPos, zPos, &shadowHeight, &solidity)) { - return NULL; - } - - f32 distFromShadow = yPos - shadowHeight; - switch (shadowType) { - case SHADOW_SQUARE_PERMANENT: shadowRadius = shadowScale / 2; break; - case SHADOW_SQUARE_SCALABLE: shadowRadius = scale_shadow_with_distance(shadowScale, distFromShadow) / 2.0f; break; - case SHADOW_SQUARE_TOGGLABLE: shadowRadius = disable_shadow_with_distance(shadowScale, distFromShadow) / 2.0f; break; - default: return NULL; - } - - return create_shadow_rectangle(shadowRadius, shadowRadius, -distFromShadow, solidity); -} - -/** - * Create a rectangular shadow whose parameters have been hardcoded in the - * `rectangles` array. - */ -Gfx *create_shadow_hardcoded_rectangle(f32 xPos, f32 yPos, f32 zPos, UNUSED s16 shadowScale, u8 solidity, s8 shadowType) { - f32 shadowHeight; - f32 halfWidth, halfLength; - s8 idx = shadowType - SHADOW_RECTANGLE_HARDCODED_OFFSET; - - if (get_shadow_height_solidity(xPos, yPos, zPos, &shadowHeight, &solidity)) { - return NULL; - } - - f32 distFromShadow = yPos - shadowHeight; - /** - * Note that idx could be negative or otherwise out of the bounds of - * the `rectangles` array. In practice, it never is, because this was - * only used twice. - */ - if (rectangles[idx].scaleWithDistance == TRUE) { - halfWidth = scale_shadow_with_distance(rectangles[idx].halfWidth, distFromShadow); - halfLength = scale_shadow_with_distance(rectangles[idx].halfLength, distFromShadow); - } else { - // This code is never used because the third element of the rectangle struct is always TRUE. - halfWidth = rectangles[idx].halfWidth; - halfLength = rectangles[idx].halfLength; - } - return create_shadow_rectangle(halfWidth, halfLength, -distFromShadow, solidity); +static void add_shadow_to_display_list(Gfx *displayListHead, s8 shadowType) { + gSPDisplayList(displayListHead++, (shadowType ? dl_shadow_square : dl_shadow_circle)); + gDPSetEnvColor(displayListHead++, 255, 255, 255, s->solidity); + gSPDisplayList(displayListHead++, dl_shadow_end); + gSPEndDisplayList(displayListHead); } +// TODO: +// - Breakout create_shadow_below_xyz into multiple functions +// - Use 'shifted' parameter again /** * Create a shadow at the absolute position given, with the given parameters. * Return a pointer to the display list representing the shadow. */ -Gfx *create_shadow_below_xyz(f32 xPos, f32 yPos, f32 zPos, s16 shadowScale, u8 shadowSolidity, s8 shadowType) { - Gfx *displayList = NULL; - struct Surface *pfloor; - find_floor(xPos, yPos, zPos, &pfloor); - gShadowFlags = SHADOW_FLAGS_NONE; - if (pfloor != NULL) { - if (pfloor->type == SURFACE_ICE) { - gShadowFlags |= SHADOW_FLAG_ICE_CARPET; +Gfx *create_shadow_below_xyz(Vec3f pos, s16 shadowScale, u8 shadowSolidity, s8 shadowType) { + struct Object *obj = gCurGraphNodeObjectNode; + // Check if the object exists. + if (obj == NULL) { + return NULL; + } + + register f32 x = pos[0]; + register f32 y = pos[1]; + register f32 z = pos[2]; + s8 isPlayer = (obj == gMarioObject); + s8 notHeldObj = (gCurGraphNodeHeldObject == NULL); + + // Attempt to use existing floors before finding a new one. + if (notHeldObj && isPlayer && gMarioState->floor) { + // Object is player and has a referenced floor. + s->floor = gMarioState->floor; + s->floorHeight = gMarioState->floorHeight; + } else if (notHeldObj && (gCurGraphNodeObject != &gMirrorMario) && obj->oFloor) { + // Object is not player but has a referenced floor. + //! Some objects only get their oFloor from bhv_init_room, which skips dynamic floors. + s->floor = obj->oFloor; + s->floorHeight = obj->oFloorHeight; + } else { + // Object has no referenced floor, so find a new one. + // gCollisionFlags |= COLLISION_FLAG_RETURN_FIRST; + s->floorHeight = find_floor(x, y, z, &s->floor); + // No shadow if OOB. + if (s->floor == NULL) { + return NULL; } - sSurfaceTypeBelowShadow = pfloor->type; } - switch (shadowType) { - case SHADOW_CIRCLE_9_VERTS: displayList = create_shadow_circle_9_verts (xPos, yPos, zPos, shadowScale, shadowSolidity ); break; - case SHADOW_CIRCLE_4_VERTS: displayList = create_shadow_circle_4_verts (xPos, yPos, zPos, shadowScale, shadowSolidity ); break; - case SHADOW_CIRCLE_4_VERTS_FLAT_UNUSED: displayList = create_shadow_circle_assuming_flat_ground(xPos, yPos, zPos, shadowScale, shadowSolidity ); break; // unused shadow type - case SHADOW_SQUARE_PERMANENT: displayList = create_shadow_square (xPos, yPos, zPos, shadowScale, shadowSolidity, shadowType ); break; - case SHADOW_SQUARE_SCALABLE: displayList = create_shadow_square (xPos, yPos, zPos, shadowScale, shadowSolidity, shadowType ); break; - case SHADOW_SQUARE_TOGGLABLE: displayList = create_shadow_square (xPos, yPos, zPos, shadowScale, shadowSolidity, shadowType ); break; - case SHADOW_CIRCLE_PLAYER: displayList = create_shadow_player (xPos, yPos, zPos, shadowScale, shadowSolidity, /* isLuigi */ FALSE); break; - default: displayList = create_shadow_hardcoded_rectangle (xPos, yPos, zPos, shadowScale, shadowSolidity, shadowType ); break; + + // Read the floor's normals + struct Surface *floor = s->floor; + register f32 nx = floor->normal.x; + register f32 ny = floor->normal.y; + register f32 nz = floor->normal.z; + + f32 distToShadow = (pos[1] - s->floorHeight); + // Hide shadow if the object is below it. + if (distToShadow < -80.0f) { + return NULL; } + + // Hide shadow if the object is too high. + if (distToShadow > 1024.0f) { + return NULL; + } + + vec3f_set(s->floorNormal, nx, ny, nz); + + // Check for ice floor, which is usually a transparent surface. + s->flags = SHADOW_FLAGS_NONE; + if (s->floor->type == SURFACE_ICE) { + s->flags |= SHADOW_FLAG_ICE; + } + + if (isPlayer) { + // Set the shadow solidity manually for certain Mario animations. + s32 solidityAction = correct_shadow_solidity_for_animations(shadowSolidity); + switch (solidityAction) { + case SHADOW_SOLIDITY_NO_SHADOW: + return NULL; + case SHADOW_SOILDITY_ALREADY_SET: + if (init_shadow(pos, shadowScale, shadowType, /* overwriteSolidity */ 0)) { + return NULL; + } + break; + case SHADOW_SOLIDITY_NOT_YET_SET: + if (init_shadow(pos, shadowScale, shadowType, shadowSolidity)) { + return NULL; + } + break; + default: + return NULL; + } + // Update s->flags and raise the shadow 5 units on the flying carpet so it doesn't clip into it. + if (gCurrLevelNum == LEVEL_RR + && floor->object != NULL + && floor->object->behavior == segmented_to_virtual(bhvPlatformOnTrack)) { + s->floorHeight += 5; + s->flags |= SHADOW_FLAG_CARPET; + } + correct_lava_shadow_height(); + } else { + if (init_shadow(pos, shadowScale, shadowType, shadowSolidity)) { + return NULL; + } + + // Get the scaling modifiers for rectangular shadows (Whomp and Spindel). + if (shadowType >= SHADOW_RECTANGLE_HARDCODED_OFFSET) { + s8 idx = shadowType - SHADOW_RECTANGLE_HARDCODED_OFFSET; + s->scale[0] *= sShadowRectangles[idx].scaleX; + s->scale[2] *= sShadowRectangles[idx].scaleZ; + } + } + + Gfx *displayList = alloc_display_list(4 * sizeof(Gfx)); + + if (displayList == NULL) { + return NULL; + } + + // Generate the shadow display list with type and solidity. + add_shadow_to_display_list(displayList, shadowType); + + // Move the shadow position to the floor height. + pos[1] = s->floorHeight; + return displayList; } diff --git a/src/game/shadow.h b/src/game/shadow.h index e78d72cf4..370253c0e 100644 --- a/src/game/shadow.h +++ b/src/game/shadow.h @@ -4,52 +4,96 @@ #include #include +#include "types.h" + /** * Shadow types. Shadows are circles, squares, or hardcoded rectangles, and * can be composed of either 4 or 9 vertices. */ -enum ShadowType -{ - SHADOW_CIRCLE_9_VERTS = 0, - SHADOW_CIRCLE_4_VERTS = 1, - SHADOW_CIRCLE_4_VERTS_FLAT_UNUSED = 2, - SHADOW_SQUARE_PERMANENT = 10, - SHADOW_SQUARE_SCALABLE = 11, - SHADOW_SQUARE_TOGGLABLE = 12, +enum ShadowType { + SHADOW_CIRCLE = 0, + SHADOW_CIRCLE_PLAYER = SHADOW_CIRCLE, + SHADOW_CIRCLE_4_VERTS = SHADOW_CIRCLE, + SHADOW_CIRCLE_9_VERTS = SHADOW_CIRCLE, + SHADOW_SQUARE = 1, + SHADOW_SQUARE_PERMANENT = 2, + SHADOW_SQUARE_SCALABLE = SHADOW_SQUARE, + SHADOW_SQUARE_TOGGLABLE = SHADOW_SQUARE, /** * This defines an offset after which rectangular shadows with custom * widths and heights can be defined. */ - SHADOW_RECTANGLE_HARDCODED_OFFSET = 50, - SHADOW_CIRCLE_PLAYER = 99 + SHADOW_RECTANGLE_HARDCODED_OFFSET = 10, + SHADOW_RECTANGLE_SPINDEL = (0 + SHADOW_RECTANGLE_HARDCODED_OFFSET), + SHADOW_RECTANGLE_WHOMP = (1 + SHADOW_RECTANGLE_HARDCODED_OFFSET), }; -#define SHADOW_FLAGS_NONE (0 << 0) -/** - * Flag for if the current shadow is above water or lava. - */ -#define SHADOW_FLAG_WATER_BOX (1 << 0) -#define SHADOW_FLAG_WATER_SURFACE (1 << 1) -/** - * Flag for if Mario is on ice or a flying carpet. - */ -#define SHADOW_FLAG_ICE_CARPET (1 << 2) -/** - * Flag for if Mario is on a flying carpet. - */ -#define SHADOW_FLAG_RAISED (1 << 3) - -extern s8 gShadowFlags; +enum ShadowFlags { + SHADOW_FLAGS_NONE = (0 << 0), + // Flag for if the current shadow is above water or lava. + SHADOW_FLAG_WATER_BOX = (1 << 0), + SHADOW_FLAG_WATER_SURFACE = (1 << 1), + // Flag for if Mario is on ice. + SHADOW_FLAG_ICE = (1 << 2), + // Flag for if Mario is on a flying carpet. + SHADOW_FLAG_CARPET = (1 << 3), +}; +#define SHADOW_FLAGS_TRANSPARENT (SHADOW_FLAG_WATER_BOX | SHADOW_FLAG_WATER_SURFACE | SHADOW_FLAG_ICE | SHADOW_FLAG_CARPET) /** - * The surface type below the current shadow. + * Encapsulation of information about a shadow. */ -extern s16 sSurfaceTypeBelowShadow; +struct Shadow { + /* The y-position of the floor (or water or lava) underneath the object. */ + f32 floorHeight; + /* The floor underneath the object. */ + struct Surface *floor; + /* Normal vector of the floor. */ + Vec3f floorNormal; + /* Size of the shadow. */ + Vec3f scale; + /* Initial solidity of the shadow, from 0 to 255 (just an alpha value). */ + Alpha solidity; + /* Various flags. */ + s8 flags; +}; + +enum ShadowSolidity { + /** + * Constant to indicate that a shadow should not be drawn. + * This is used to disable shadows during specific frames of Mario's + * animations. + */ + SHADOW_SOLIDITY_NO_SHADOW, + /** + * Constant to indicate that a shadow's solidity has been pre-set by a previous + * function and should not be overwritten. + */ + SHADOW_SOILDITY_ALREADY_SET, + /** + * Constant to indicate that a shadow's solidity has not yet been set. + */ + SHADOW_SOLIDITY_NOT_YET_SET, +}; + +/** + * A struct containing info about hardcoded rectangle shadows. + */ +typedef struct { + /* X scale of the rectangle. */ + f32 scaleX; + /* Z scale of the rectangle. */ + f32 scaleZ; + /* Flag for if this shadow be smaller when its object is further away. */ + u8 scaleWithDistance : 1; +} ShadowRectangle; + +extern struct Shadow gCurrShadow; /** * Given the (x, y, z) location of an object, create a shadow below that object * with the given initial solidity and "shadowType" (described above). */ -Gfx *create_shadow_below_xyz(f32 xPos, f32 yPos, f32 zPos, s16 shadowScale, u8 shadowSolidity, s8 shadowType); +Gfx *create_shadow_below_xyz(Vec3f pos, s16 shadowScale, u8 shadowSolidity, s8 shadowType); #endif // SHADOW_H