sm64pc/src/game/shadow.c

903 lines
31 KiB
C

#include <ultra64.h>
#include <math.h>
#include "sm64.h"
#include "shadow.h"
#include "area.h"
#include "engine/graph_node.h"
#include "engine/math_util.h"
#include "engine/surface_collision.h"
#include "mario_animation_ids.h"
#include "mario.h"
#include "memory.h"
#include "rendering_graph_node.h"
#include "object_list_processor.h"
#include "segment2.h"
#include "save_file.h"
#include "geo_misc.h"
#include "level_table.h"
/**
* @file shadow.c
* 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 }
};
// See shadow.h for documentation.
s8 gShadowAboveWaterOrLava;
s8 gMarioOnIceOrCarpet;
s8 sMarioOnFlyingCarpet;
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;
*newZ = oldZ * coss(obj->oFaceAngleYaw) - oldX * sins(obj->oFaceAngleYaw);
*newX = oldZ * sins(obj->oFaceAngleYaw) + oldX * coss(obj->oFaceAngleYaw);
}
/**
* 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);
}
/**
* 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.0) {
newScale = initial;
} else if (distFromFloor >= 600.0) {
newScale = initial * 0.5;
} else {
newScale = initial * (1.0 - (0.5 * distFromFloor / 600.0));
}
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.0) {
return 0.0f;
} else {
return shadowScale;
}
}
/**
* Dim a shadow when its parent object is further from the ground.
*/
u8 dim_shadow_with_distance(u8 solidity, f32 distFromFloor) {
f32 ret;
if (solidity < 121) {
return solidity;
} else if (distFromFloor <= 0.0) {
return solidity;
} else if (distFromFloor >= 600.0) {
return 120;
} else {
ret = ((120 - solidity) * distFromFloor) / 600.0 + (f32) solidity;
return ret;
}
}
/**
* 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) {
f32 waterLevel = find_water_level(s->parentX, s->parentZ);
if (waterLevel < -10000.0) {
return 0;
} else if (s->parentY >= waterLevel && s->floorHeight <= waterLevel) {
gShadowAboveWaterOrLava = TRUE;
return waterLevel;
}
//! @bug Missing return statement. This compiles to return `waterLevel`
//! incidentally.
}
/**
* Initialize a shadow. Return 0 on success, 1 on failure.
*
* @param xPos,yPos,zPos 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
*/
s8 init_shadow(struct Shadow *s, f32 xPos, f32 yPos, f32 zPos, s16 shadowScale, u8 overwriteSolidity) {
f32 waterLevel;
f32 floorSteepness;
struct FloorGeometry *floorGeometry;
s->parentX = xPos;
s->parentY = yPos;
s->parentZ = zPos;
s->floorHeight = find_floor_height_and_data(s->parentX, s->parentY, s->parentZ, &floorGeometry);
if (gEnvironmentRegions != 0) {
waterLevel = get_water_level_below_shadow(s);
}
if (gShadowAboveWaterOrLava) {
//! @bug Use of potentially undefined variable `waterLevel`
s->floorHeight = waterLevel;
// Assume that the water is flat.
s->floorNormalX = 0;
s->floorNormalY = 1.0;
s->floorNormalZ = 0;
s->floorOriginOffset = -waterLevel;
} 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 < -10000.0 || floorGeometry->normalY <= 0.0) {
return 1;
}
s->floorNormalX = floorGeometry->normalX;
s->floorNormalY = floorGeometry->normalY;
s->floorNormalZ = floorGeometry->normalZ;
s->floorOriginOffset = floorGeometry->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);
floorSteepness = sqrtf(s->floorNormalX * s->floorNormalX + s->floorNormalZ * s->floorNormalZ);
// This if-statement avoids dividing by 0.
if (floorSteepness == 0.0) {
s->floorTilt = 0;
} else {
s->floorTilt = 90.0 - 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, textureY;
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 (sMarioOnFlyingCarpet) {
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;
f32 halfScale;
f32 halfTiltedScale;
s8 xCoordUnit;
s8 zCoordUnit;
struct FloorGeometry *dummy;
// This makes xCoordUnit and yCoordUnit each one of -1, 0, or 1.
get_vertex_coords(index, shadowVertexType, &xCoordUnit, &zCoordUnit);
halfScale = (xCoordUnit * s.shadowScale) / 2.0;
halfTiltedScale = (zCoordUnit * tiltedScale) / 2.0;
*xPosVtx = (halfTiltedScale * sinf(downwardAngle)) + (halfScale * cosf(downwardAngle)) + s.parentX;
*zPosVtx = (halfTiltedScale * cosf(downwardAngle)) - (halfScale * sinf(downwardAngle)) + s.parentZ;
if (gShadowAboveWaterOrLava) {
*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_and_data(*xPosVtx, s.parentY, *zPosVtx, &dummy);
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;
}
}
}
/**
* 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_height_and_data`. (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;
f32 ret = (relX * s.floorNormalX) + (relY * s.floorNormalY) + (relZ * s.floorNormalZ);
return ret;
}
/**
* 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 (gShadowAboveWaterOrLava != 0) {
solidity = 200;
}
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_height_and_data`, 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 && !gShadowAboveWaterOrLava
&& 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);
}
/**
* 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) {
if (curr >= 0 && curr < start) {
s->solidity = 0;
} else if (end < curr) {
s->solidity = finalSolidity;
} else {
s->solidity = (f32) finalSolidity * (curr - start) / (end - start);
}
}
/**
* Linearly interpolate a shadow's solidity between initialSolidity and zero
* 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) {
// 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,
// with start == 0.
if (curr >= start && end >= curr) {
s->solidity = ((f32) initialSolidity * (1.0 - (f32)(curr - start) / (end - start)));
} else {
s->solidity = 0;
}
}
/**
* Change a shadow's solidity based on the player's current animation frame.
*/
s8 correct_shadow_solidity_for_animations(s32 isLuigi, u8 initialSolidity, struct Shadow *shadow) {
struct Object *player;
s8 ret;
s16 animFrame;
switch (isLuigi) {
case 0:
player = gMarioObject;
break;
case 1:
/**
* 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;
break;
}
animFrame = player->header.gfx.unk38.animFrame;
switch (player->header.gfx.unk38.animID) {
case MARIO_ANIM_IDLE_ON_LEDGE:
ret = SHADOW_SOLIDITY_NO_SHADOW;
break;
case MARIO_ANIM_FAST_LEDGE_GRAB:
linearly_interpolate_solidity_positive(shadow, initialSolidity, animFrame, 5, 14);
ret = SHADOW_SOILDITY_ALREADY_SET;
break;
case MARIO_ANIM_SLOW_LEDGE_GRAB:
linearly_interpolate_solidity_positive(shadow, initialSolidity, animFrame, 21, 33);
ret = SHADOW_SOILDITY_ALREADY_SET;
break;
case MARIO_ANIM_CLIMB_DOWN_LEDGE:
linearly_interpolate_solidity_negative(shadow, initialSolidity, animFrame, 0, 5);
ret = SHADOW_SOILDITY_ALREADY_SET;
break;
default:
ret = SHADOW_SOLIDITY_NOT_YET_SET;
break;
}
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.0) {
s->floorHeight = -3062.0;
gShadowAboveWaterOrLava = TRUE;
} else if (s->floorHeight > 3400.0) {
s->floorHeight = 3492.0;
gShadowAboveWaterOrLava = TRUE;
}
} else if (gCurrLevelNum == LEVEL_LLL && gCurrAreaIndex == 1
&& sSurfaceTypeBelowShadow == SURFACE_BURNING) {
s->floorHeight = 5.0;
gShadowAboveWaterOrLava = TRUE;
}
}
/**
* 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) {
Vtx *verts;
Gfx *displayList;
struct Shadow shadow;
s8 ret;
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:
gMarioOnIceOrCarpet = 1;
sMarioOnFlyingCarpet = 1;
break;
case FLYING_CARPET_MOVING_WITH_MARIO:
gMarioOnIceOrCarpet = 1;
break;
}
}
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;
}
verts = alloc_display_list(9 * sizeof(Vtx));
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;
}
/**
* Create a circular shadow composed of 9 vertices.
*/
Gfx *create_shadow_circle_9_verts(f32 xPos, f32 yPos, f32 zPos, s16 shadowScale, u8 solidity) {
Vtx *verts;
Gfx *displayList;
struct Shadow shadow;
s32 i;
if (init_shadow(&shadow, xPos, yPos, zPos, shadowScale, solidity) != 0) {
return NULL;
}
verts = alloc_display_list(9 * sizeof(Vtx));
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) {
Vtx *verts;
Gfx *displayList;
struct Shadow shadow;
s32 i;
if (init_shadow(&shadow, xPos, yPos, zPos, shadowScale, solidity) != 0) {
return NULL;
}
verts = alloc_display_list(4 * sizeof(Vtx));
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) {
Vtx *verts;
Gfx *displayList;
struct FloorGeometry *dummy; // only for calling find_floor_height_and_data
f32 distBelowFloor;
f32 floorHeight = find_floor_height_and_data(xPos, yPos, zPos, &dummy);
f32 radius = shadowScale / 2;
if (floorHeight < -10000.0) {
return NULL;
} else {
distBelowFloor = floorHeight - yPos;
}
verts = alloc_display_list(4 * sizeof(Vtx));
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) {
struct FloorGeometry *dummy;
f32 waterLevel;
*shadowHeight = find_floor_height_and_data(xPos, yPos, zPos, &dummy);
if (*shadowHeight < -10000.0) {
return 1;
} else {
waterLevel = find_water_level(xPos, zPos);
if (waterLevel < -10000.0) {
// Dead if-statement. There may have been an assert here.
} else if (yPos >= waterLevel && waterLevel >= *shadowHeight) {
gShadowAboveWaterOrLava = TRUE;
*shadowHeight = waterLevel;
*solidity = 200;
}
}
return 0;
}
/**
* 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 distFromShadow;
f32 shadowRadius;
if (get_shadow_height_solidity(xPos, yPos, zPos, &shadowHeight, &solidity) != 0) {
return NULL;
}
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.0;
break;
case SHADOW_SQUARE_TOGGLABLE:
shadowRadius = disable_shadow_with_distance(shadowScale, distFromShadow) / 2.0;
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 distFromShadow;
f32 halfWidth;
f32 halfLength;
s8 idx = shadowType - SHADOW_RECTANGLE_HARDCODED_OFFSET;
if (get_shadow_height_solidity(xPos, yPos, zPos, &shadowHeight, &solidity) != 0) {
return NULL;
}
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);
}
/**
* 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);
gShadowAboveWaterOrLava = FALSE;
gMarioOnIceOrCarpet = 0;
sMarioOnFlyingCarpet = 0;
if (pfloor != NULL) {
if (pfloor->type == SURFACE_ICE) {
gMarioOnIceOrCarpet = 1;
}
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: // unused shadow type
displayList = create_shadow_circle_assuming_flat_ground(xPos, yPos, zPos, shadowScale,
shadowSolidity);
break;
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;
}
return displayList;
}