#include <ultra64.h>

#include "sm64.h"
#include "game.h"
#include "mario.h"
#include "memory.h"
#include "save_file.h"
#include "engine/surface_collision.h"
#include "engine/graph_node.h"
#include "geo_misc.h"
#include "area.h"
#include "segment2.h"
#include "paintings.h"

s16 gPaintingMarioFloorType;
float gPaintingMarioXPos, gPaintingMarioYPos, gPaintingMarioZPos;
struct Thing *D_8035FFA0;
Vec3f *D_8035FFA4;
struct Painting *ripplingPainting;
s8 dddStatus;

struct Painting *hmcPaintings[] = {
    &cotmc_painting,
    NULL,
};

struct Painting *insideCastlePaintings[] = {
    &bob_painting, &ccm_painting, &wf_painting,  &jrb_painting,      &lll_painting,
    &ssl_painting, &hmc_painting, &ddd_painting, &wdw_painting,      &thi_tiny_painting,
    &ttm_painting, &ttc_painting, &sl_painting,  &thi_huge_painting, NULL,
};

struct Painting *ttmPaintings[] = {
    &ttm_slide_painting,
    NULL,
};

struct Painting **paintingGroups[] = {
    hmcPaintings,
    insideCastlePaintings,
    ttmPaintings,
};

s16 gPaintingUpdateCounter = 1;
s16 gLastPaintingUpdateCounter = 0;

void stopAllRippleExcept(s16 *idptr, struct Painting *paintingGroup[]) {
    s16 index;
    s16 id = *idptr;

    index = 0;
    while (paintingGroup[index] != NULL) // for each painting
    {
        struct Painting *painting = segmented_to_virtual(paintingGroup[index]);
        if (painting->id != id) {
            painting->rippleStatus = 0; // stop all rippling except for the selected painting
        }
        index++;
    }
}

float find_mario_y_position_on_painting(struct Painting *painting) {
    //! unnecessary use of double constants
    float marioYOffsetFromPainting = gPaintingMarioYPos - painting->vYPos + 50.0;

    if (marioYOffsetFromPainting < 0.0) {
        marioYOffsetFromPainting = 0.0; // If Mario is below the bottom, return the bottom
    } else if (marioYOffsetFromPainting
               > painting->vSize) { // If Mario is above the top, return the top
        marioYOffsetFromPainting = painting->vSize;
    }
    return marioYOffsetFromPainting;
}

float find_mario_z_position_on_painting(struct Painting *painting) {
    float marioZOffsetFromPainting = painting->vZPos - gPaintingMarioZPos;

    if (marioZOffsetFromPainting < 0.0) {
        marioZOffsetFromPainting = 0.0; // If Mario is past the left side, return the left side
    } else if (marioZOffsetFromPainting > painting->vSize) {
        marioZOffsetFromPainting =
            painting->vSize; // If Mario is past the right side, return the right side
    }
    return marioZOffsetFromPainting;
}

float painting_find_vertical_ripple_location(struct Painting *painting, s8 rippleSpot) {
    switch (rippleSpot) {
        case MARIO_Y:
            return find_mario_y_position_on_painting(painting); // normal vertical paintings
            break;
        case MARIO_Z:
            return find_mario_z_position_on_painting(painting); // horizontal paintings use X and Z
            break;
        case MIDDLE_Y:
            return painting->vSize / 2.0; // some concentric ripples don't care about Mario
            break;
    }
}

float find_part_of_painting_near_mario(struct Painting *painting) {
    float firstQuarter = painting->vSize / 4.0;       // 1/4 of the way across the painting
    float secondQuarter = painting->vSize / 2.0;      // 1/2 of the way across the painting
    float thirdQuarter = painting->vSize * 3.0 / 4.0; // 3/4 of the way across the painting

    if (painting->floorEntered & STAND_LEFT) {
        return firstQuarter;
    } else if (painting->floorEntered & STAND_MIDDLE) {
        return secondQuarter;
    } else if (painting->floorEntered & STAND_RIGHT) {
        return thirdQuarter;
    } else if (painting->floorEntered & ENTER_LEFT) {
        return firstQuarter;
    } else if (painting->floorEntered & ENTER_MIDDLE) {
        return secondQuarter;
    } else if (painting->floorEntered & ENTER_RIGHT) {
        return thirdQuarter;
    }
}

float find_mario_x_position_on_painting(struct Painting *painting) {
    float mario_x_offset_from_painting = gPaintingMarioXPos - painting->vXPos;

    if (mario_x_offset_from_painting < 0.0) {
        mario_x_offset_from_painting = 0.0; // If Mario is past the left side, return the left side
    } else if (mario_x_offset_from_painting > painting->vSize) {
        mario_x_offset_from_painting =
            painting->vSize; // If Mario is past the right side, return the right side
    }
    return mario_x_offset_from_painting;
}

float painting_find_horizontal_ripple_location(struct Painting *painting, s8 rippleSpot) {
    switch (rippleSpot) {
        case NEAR_MARIO_LATERALLY: // normal vertical paintings
            return find_part_of_painting_near_mario(painting);
            break;
        case MARIO_X: // horizontally placed paintings use X and Z
            return find_mario_x_position_on_painting(painting);
            break;
        case MIDDLE_X: // concentric rippling may not care about Mario
            return painting->vSize / 2.0;
            break;
    }
}

void painting_set_ripple_type(s8 intendedStatus, struct Painting *painting,
                              struct Painting *paintingGroup[], s8 hRippleSpot, s8 vRippleSpot,
                              s8 resetTimer) {
    stopAllRippleExcept(&painting->id, paintingGroup); // make sure no other paintings are rippling
    switch (intendedStatus) // set the variables necessary for the given ripple status
    {
        case RIPPLE_STATE_IDLE:
            painting->currRippleMag = painting->passiveRippleMag;
            painting->rippleMagMultiplier = painting->passiveRippleMagMultiplier;
            painting->currRippleRate = painting->passiveRippleRate;
            painting->dispersionFactor = painting->passiveDispersionFactor;
            break;
        case RIPPLE_STATE_ENTRY:
            painting->currRippleMag = painting->entryRippleMag;
            painting->rippleMagMultiplier = painting->entryRippleMagMultiplier;
            painting->currRippleRate = painting->entryRippleRate;
            painting->dispersionFactor = painting->entryDispersionFactor;
            break;
    }
    painting->rippleStatus = intendedStatus;
    painting->horizontalRippleSpot =
        painting_find_horizontal_ripple_location(painting, hRippleSpot); // find the ripple location
    painting->verticalRippleSpot = painting_find_vertical_ripple_location(painting, vRippleSpot);
    gPaintingMarioYEntry = gPaintingMarioYPos;
    if (resetTimer == RESET_TIMER) {
        painting->rippleTimer = 0.0f;
    }
    ripplingPainting = painting;
}

void vertical_proximity_ripple_painting_ripple(
    struct Painting *painting,
    struct Painting
        *paintingGroup[]) // For paintings aligned vertically that follow RIPPLE_TRIGGER_PROXIMITY, set
                          // some flags depending on where Mario is
{
    if (painting->floorEntered & STAND_LEFT) {
        painting_set_ripple_type(RIPPLE_STATE_IDLE, painting, paintingGroup, NEAR_MARIO_LATERALLY,
                                 MARIO_Y, RESET_TIMER);
    } else if (painting->floorEntered & STAND_MIDDLE) {
        painting_set_ripple_type(RIPPLE_STATE_IDLE, painting, paintingGroup, NEAR_MARIO_LATERALLY,
                                 MARIO_Y, RESET_TIMER);
    } else if (painting->floorEntered & STAND_RIGHT) {
        painting_set_ripple_type(RIPPLE_STATE_IDLE, painting, paintingGroup, NEAR_MARIO_LATERALLY,
                                 MARIO_Y, RESET_TIMER);
    } else if (painting->floorEntered & ENTER_LEFT) {
        painting_set_ripple_type(RIPPLE_STATE_ENTRY, painting, paintingGroup, NEAR_MARIO_LATERALLY,
                                 MARIO_Y, RESET_TIMER);
    } else if (painting->floorEntered & ENTER_MIDDLE) {
        painting_set_ripple_type(RIPPLE_STATE_ENTRY, painting, paintingGroup, NEAR_MARIO_LATERALLY,
                                 MARIO_Y, RESET_TIMER);
    } else if (painting->floorEntered & ENTER_RIGHT) {
        painting_set_ripple_type(RIPPLE_STATE_ENTRY, painting, paintingGroup, NEAR_MARIO_LATERALLY,
                                 MARIO_Y, RESET_TIMER);
    }
}

void vertical_proximity_ripple_painting_ripple_if_mario_enters(
    struct Painting *painting,
    struct Painting *paintingGroup[]) // For paintings aligned vertically that follow
                                          // RIPPLE_TRIGGER_PROXIMITY, set some flags if Mario enters
{
    if (painting->floorEntered & ENTER_LEFT) {
        painting_set_ripple_type(RIPPLE_STATE_ENTRY, painting, paintingGroup, NEAR_MARIO_LATERALLY,
                                 MARIO_Y, RESET_TIMER);
    } else if (painting->floorEntered & ENTER_MIDDLE) {
        painting_set_ripple_type(RIPPLE_STATE_ENTRY, painting, paintingGroup, NEAR_MARIO_LATERALLY,
                                 MARIO_Y, RESET_TIMER);
    } else if (painting->floorEntered & ENTER_RIGHT) {
        painting_set_ripple_type(RIPPLE_STATE_ENTRY, painting, paintingGroup, NEAR_MARIO_LATERALLY,
                                 MARIO_Y, RESET_TIMER);
    }
}

void vertical_continuous_ripple_painting_ripple(
    struct Painting *painting,
    struct Painting
        *paintingGroup[]) // For paintings aligned vertically that follow RIPPLE_TRIGGER_CONTINUOUS (DDD
                          // only), set some flags depending on where Mario is
{
    if (painting->floorEntered & STAND_LEFT) {
        painting_set_ripple_type(RIPPLE_STATE_IDLE, painting, paintingGroup, MIDDLE_X, MIDDLE_Y,
                                 RESET_TIMER);
    } else if (painting->floorEntered & STAND_MIDDLE) {
        painting_set_ripple_type(RIPPLE_STATE_IDLE, painting, paintingGroup, MIDDLE_X, MIDDLE_Y,
                                 RESET_TIMER);
    } else if (painting->floorEntered & STAND_RIGHT) {
        painting_set_ripple_type(RIPPLE_STATE_IDLE, painting, paintingGroup, MIDDLE_X, MIDDLE_Y,
                                 RESET_TIMER);
    } else if (painting->floorEntered & ENTER_LEFT) {
        painting_set_ripple_type(RIPPLE_STATE_ENTRY, painting, paintingGroup, NEAR_MARIO_LATERALLY,
                                 MARIO_Y, RESET_TIMER);
    } else if (painting->floorEntered & ENTER_MIDDLE) {
        painting_set_ripple_type(RIPPLE_STATE_ENTRY, painting, paintingGroup, NEAR_MARIO_LATERALLY,
                                 MARIO_Y, RESET_TIMER);
    } else if (painting->floorEntered & ENTER_RIGHT) {
        painting_set_ripple_type(RIPPLE_STATE_ENTRY, painting, paintingGroup, NEAR_MARIO_LATERALLY,
                                 MARIO_Y, RESET_TIMER);
    }
}

void vertical_continuous_ripple_painting_ripple_if_mario_enters(
    struct Painting *painting,
    struct Painting
        *paintingGroup[]) // For paintings aligned vertically that follow RIPPLE_TRIGGER_CONTINUOUS (DDD
                          // only), set some flags if Mario enters
{
    if (painting->floorEntered & ENTER_LEFT) {
        painting_set_ripple_type(RIPPLE_STATE_ENTRY, painting, paintingGroup, NEAR_MARIO_LATERALLY,
                                 MARIO_Y, DONT_RESET_TIMER);
    } else if (painting->floorEntered & ENTER_MIDDLE) {
        painting_set_ripple_type(RIPPLE_STATE_ENTRY, painting, paintingGroup, NEAR_MARIO_LATERALLY,
                                 MARIO_Y, DONT_RESET_TIMER);
    } else if (painting->floorEntered & ENTER_RIGHT) {
        painting_set_ripple_type(RIPPLE_STATE_ENTRY, painting, paintingGroup, NEAR_MARIO_LATERALLY,
                                 MARIO_Y, DONT_RESET_TIMER);
    }
}

void horizontal_proximity_ripple_painting_ripple(
    struct Painting *painting,
    struct Painting
        *paintingGroup[]) // For paintings aligned horizontally that follow RIPPLE_TRIGGER_PROXIMITY
                          // (these are not found in-game), set some flags depending on where Mario is
{
    if (painting->floorEntered & STAND_LEFT) {
        painting_set_ripple_type(RIPPLE_STATE_IDLE, painting, paintingGroup, MARIO_X, MARIO_Z,
                                 RESET_TIMER);
    } else if (painting->floorEntered & STAND_MIDDLE) {
        painting_set_ripple_type(RIPPLE_STATE_IDLE, painting, paintingGroup, MARIO_X, MARIO_Z,
                                 RESET_TIMER);
    } else if (painting->floorEntered & STAND_RIGHT) {
        painting_set_ripple_type(RIPPLE_STATE_IDLE, painting, paintingGroup, MARIO_X, MARIO_Z,
                                 RESET_TIMER);
    } else if (painting->marioNewlyUnderPainting) {
        if (painting->currFloor & ENTER_LEFT) {
            painting_set_ripple_type(RIPPLE_STATE_ENTRY, painting, paintingGroup, MARIO_X, MARIO_Z,
                                     RESET_TIMER);
        } else if (painting->currFloor & ENTER_MIDDLE) {
            painting_set_ripple_type(RIPPLE_STATE_ENTRY, painting, paintingGroup, MARIO_X, MARIO_Z,
                                     RESET_TIMER);
        } else if (painting->currFloor & ENTER_RIGHT) {
            painting_set_ripple_type(RIPPLE_STATE_ENTRY, painting, paintingGroup, MARIO_X, MARIO_Z,
                                     RESET_TIMER);
        }
    }
}

void horizontal_proximity_ripple_painting_ripple_if_mario_enters(
    struct Painting *painting,
    struct Painting
        *paintingGroup[]) // For paintings aligned horizontally that follow RIPPLE_TRIGGER_PROXIMITY
                          // (these are not found in-game), set some flags if Mario enters
{
    if (painting->marioNewlyUnderPainting) {
        if (painting->currFloor & ENTER_LEFT) {
            painting_set_ripple_type(RIPPLE_STATE_ENTRY, painting, paintingGroup, MARIO_X, MARIO_Z,
                                     RESET_TIMER);
        } else if (painting->currFloor & ENTER_MIDDLE) {
            painting_set_ripple_type(RIPPLE_STATE_ENTRY, painting, paintingGroup, MARIO_X, MARIO_Z,
                                     RESET_TIMER);
        } else if (painting->currFloor & ENTER_RIGHT) {
            painting_set_ripple_type(RIPPLE_STATE_ENTRY, painting, paintingGroup, MARIO_X, MARIO_Z,
                                     RESET_TIMER);
        }
    }
}

void horizontal_continuous_ripple_painting_ripple(
    struct Painting *painting,
    struct Painting
        *paintingGroup[]) // For paintings aligned horizontally that follow RIPPLE_TRIGGER_CONTINUOUS
                          // (HMC and CotMC), set some flags depending on where Mario is
{
    if (painting->floorEntered & STAND_LEFT) {
        painting_set_ripple_type(RIPPLE_STATE_IDLE, painting, paintingGroup, MIDDLE_X, MIDDLE_Y,
                                 RESET_TIMER);
    } else if (painting->floorEntered & STAND_MIDDLE) {
        painting_set_ripple_type(RIPPLE_STATE_IDLE, painting, paintingGroup, MIDDLE_X, MIDDLE_Y,
                                 RESET_TIMER);
    } else if (painting->floorEntered & STAND_RIGHT) {
        painting_set_ripple_type(RIPPLE_STATE_IDLE, painting, paintingGroup, MIDDLE_X, MIDDLE_Y,
                                 RESET_TIMER);
    } else if (painting->currFloor & ENTER_LEFT) {
        painting_set_ripple_type(RIPPLE_STATE_ENTRY, painting, paintingGroup, MARIO_X, MARIO_Z,
                                 RESET_TIMER);
    } else if (painting->currFloor & ENTER_MIDDLE) {
        painting_set_ripple_type(RIPPLE_STATE_ENTRY, painting, paintingGroup, MARIO_X, MARIO_Z,
                                 RESET_TIMER);
    } else if (painting->currFloor & ENTER_RIGHT) {
        painting_set_ripple_type(RIPPLE_STATE_ENTRY, painting, paintingGroup, MARIO_X, MARIO_Z,
                                 RESET_TIMER);
    }
}

void horizontal_continuous_ripple_painting_ripple_if_mario_enters(
    struct Painting *painting,
    struct Painting
        *paintingGroup[]) // For paintings aligned horizontally that follow RIPPLE_TRIGGER_CONTINUOUS
                          // (HMC and CotMC), set some flags if Mario enters
{
    if (painting->marioNewlyUnderPainting) {
        if (painting->currFloor & ENTER_LEFT) {
            painting_set_ripple_type(RIPPLE_STATE_ENTRY, painting, paintingGroup, MARIO_X, MARIO_Z,
                                     DONT_RESET_TIMER);
        } else if (painting->currFloor & ENTER_MIDDLE) {
            painting_set_ripple_type(RIPPLE_STATE_ENTRY, painting, paintingGroup, MARIO_X, MARIO_Z,
                                     DONT_RESET_TIMER);
        } else if (painting->currFloor & ENTER_RIGHT) {
            painting_set_ripple_type(RIPPLE_STATE_ENTRY, painting, paintingGroup, MARIO_X, MARIO_Z,
                                     DONT_RESET_TIMER);
        }
    }
}

void painting_update_floors(struct Painting *painting) {
    s16 paintingId = painting->id;
    s8 leftSideStand = 0;
    s8 middleStand = 0;
    s8 rightSideStand = 0;
    s8 leftSideEnter = 0;
    s8 middleEnter = 0;
    s8 rightSideEnter = 0;

    /* The area in front of every painting in the game (except HMC and CotMC, which   *\
    |* act a little differently) is made up of 3 special floor triangles with special *|
    |* (unique) surface types. This code checks which surface Mario is currently on   *|
    \* and sets a bitfield accordingly.                                               */

    if (gPaintingMarioFloorType
        == paintingId * 3
               + SURFACE_PAINTING_WOBBLE_A6) { // check if Mario's current floor is one of the
                                               // special floors
        leftSideStand = STAND_LEFT;
    }
    if (gPaintingMarioFloorType == paintingId * 3 + SURFACE_PAINTING_WOBBLE_A7) {
        middleStand = STAND_MIDDLE;
    }
    if (gPaintingMarioFloorType == paintingId * 3 + SURFACE_PAINTING_WOBBLE_A8) {
        rightSideStand = STAND_RIGHT;
    }
    if (gPaintingMarioFloorType == paintingId * 3 + SURFACE_PAINTING_WARP_D3) {
        leftSideEnter = ENTER_LEFT;
    }
    if (gPaintingMarioFloorType == paintingId * 3 + SURFACE_PAINTING_WARP_D4) {
        middleEnter = ENTER_MIDDLE;
    }
    if (gPaintingMarioFloorType == paintingId * 3 + SURFACE_PAINTING_WARP_D5) {
        rightSideEnter = ENTER_RIGHT;
    }

    painting->lastFloor = painting->currFloor;
    painting->currFloor = leftSideStand + middleStand + rightSideStand + leftSideEnter + middleEnter
                          + rightSideEnter; // at most 1 of these will be nonzero;
    painting->floorEntered =
        (painting->lastFloor ^ painting->currFloor)
        & painting->currFloor; // floorEntered is true iff currFloor is true and lastFloor is false
                               // (Mario just entered the floor on this frame)
    painting->lastMarioUnderPainting = painting->currMarioUnderPainting;
    if (gPaintingMarioYPos < painting->vYPos) {
        painting->currMarioUnderPainting = 1; // If Mario is below the painting, set a variable
    } else {
        painting->currMarioUnderPainting = 0; // Otherwise, reset it
    }
    painting->marioNewlyUnderPainting =
        (painting->lastMarioUnderPainting ^ painting->currMarioUnderPainting)
        & painting->currMarioUnderPainting; // Again, marioNewlyUnderPainting is true iff he is under it
                                            // this frame but wasn't last frame.
}

void painting_update_ripple_status(struct Painting *painting) {
    if (gPaintingUpdateCounter != gLastPaintingUpdateCounter) {
        painting->currRippleMag *= painting->rippleMagMultiplier;
        painting->rippleTimer +=
            1.0; //! After ~6.47 days, paintings with RIPPLE_TRIGGER_CONTINUOUS will increment this to
                 //! 16777216 (1 << 24), at which point it will freeze (due to floating-point
                 //! imprecision?) and the painting will stop rippling. This happens to HMC, DDD, and
                 //! CotMC. This happens on Wii VC. Untested on N64 and Wii U VC.
    }
    if (painting->rippleTrigger == RIPPLE_TRIGGER_PROXIMITY) {
        if (painting->currRippleMag <= 1.0) // if the painting is barely rippling, make it stop rippling
        {
            painting->rippleStatus = RIPPLE_STATE_NONE;
            ripplingPainting = NULL;
        }
    } else if (painting->rippleTrigger == RIPPLE_TRIGGER_CONTINUOUS) {

        if (painting->rippleStatus == RIPPLE_STATE_ENTRY
            && painting->currRippleMag
                   <= painting->passiveRippleMag) // if the painting is doing the entry ripple but the
                                                  // ripples are as small as those from the passive
                                                  // ripple, make it do a passive ripple
        {
            painting->rippleStatus = RIPPLE_STATE_IDLE;
            painting->currRippleMag = painting->passiveRippleMag;
            painting->rippleMagMultiplier = painting->passiveRippleMagMultiplier;
            painting->currRippleRate = painting->passiveRippleRate;
            painting->dispersionFactor = painting->passiveDispersionFactor;
        }
    }
}

s16 painting_calculate_point_ripple(struct Painting *painting, float xpos,
                                    float ypos) // note that xpos and ypos correspond to a point on the
                                                // face of the painting, not actual axes
{
    float rippleMag = painting->currRippleMag;
    float rippleRate = painting->currRippleRate;
    float dispersionFactor = painting->dispersionFactor;
    float rippleTimer = painting->rippleTimer;
    float hRippleSpot = painting->horizontalRippleSpot;
    float vRippleSpot = painting->verticalRippleSpot;
    float distanceToRippleSpot;
    float scaledDistance;

    xpos *= painting->vSize / DEFAULT_HEIGHT; // scale x position of point by painting size
    ypos *= painting->vSize / DEFAULT_HEIGHT; // scale y position of point by painting size
    distanceToRippleSpot =
        sqrtf((xpos - hRippleSpot) * (xpos - hRippleSpot)
              + (ypos - vRippleSpot) * (ypos - vRippleSpot)); // distance from point to ripple spot
    scaledDistance =
        distanceToRippleSpot / dispersionFactor; // scale distance by dispersion factor so that ripples
                                                 // farther from the ripple spot are smaller
    if (rippleTimer < scaledDistance) {
        return 0; // if the ripple hasn't reached the point yet, make the point magnitude 0
    } else {

        float rippleHeight =
            rippleMag
            * cosf(rippleRate * (2 * M_PI)
                   * (rippleTimer
                      - scaledDistance)); // use a cosine wave to make the ripple go up and down, and
                                          // scale it by the painting's ripple magnitude

        return round_float(rippleHeight); // round it to an int and return it
    }
}

s16 painting_conditionally_calculate_point_ripple(struct Painting *painting, s16 condition,
                                                  s16 xpos, s16 ypos) {
    s16 rippleHeight = 0;

    if (condition) {
        rippleHeight = painting_calculate_point_ripple(painting, xpos, ypos);
    }
    return rippleHeight;
}

void Print1(struct Painting *painting, s16 *b, s16 c) {
    s16 sp1E;

    D_8035FFA0 = mem_pool_alloc(gEffectsMemoryPool, c * sizeof(struct Thing));
    if (D_8035FFA0 == NULL) {
    }
    for (sp1E = 0; sp1E < c; sp1E++) {
        D_8035FFA0[sp1E].unk0[0] = b[sp1E * 3 + 1];
        D_8035FFA0[sp1E].unk0[1] = b[sp1E * 3 + 2];
        D_8035FFA0[sp1E].unk0[2] = painting_conditionally_calculate_point_ripple(
            painting, b[(sp1E + 1) * 3], D_8035FFA0[sp1E].unk0[0], D_8035FFA0[sp1E].unk0[1]);
    }
}

void Print2(s16 *a, s16 b, s16 c) {
    s16 sp46;

    D_8035FFA4 = mem_pool_alloc(gEffectsMemoryPool, c * sizeof(Vec3f));
    if (D_8035FFA4 == NULL) {
    }
    for (sp46 = 0; sp46 < c; sp46++) {
        s16 sp44 = b * 3 + sp46 * 3 + 2;
        s16 sp42 = a[sp44];
        s16 sp40 = a[sp44 + 1];
        s16 sp3E = a[sp44 + 2];
        f32 sp38 = D_8035FFA0[sp42].unk0[0];
        f32 sp34 = D_8035FFA0[sp42].unk0[1];
        f32 sp30 = D_8035FFA0[sp42].unk0[2];
        f32 sp2C = D_8035FFA0[sp40].unk0[0];
        f32 sp28 = D_8035FFA0[sp40].unk0[1];
        f32 sp24 = D_8035FFA0[sp40].unk0[2];
        f32 sp20 = D_8035FFA0[sp3E].unk0[0];
        f32 sp1C = D_8035FFA0[sp3E].unk0[1];
        f32 sp18 = D_8035FFA0[sp3E].unk0[2];

        D_8035FFA4[sp46][0] = (sp28 - sp34) * (sp18 - sp24) - (sp24 - sp30) * (sp1C - sp28);
        D_8035FFA4[sp46][1] = (sp24 - sp30) * (sp20 - sp2C) - (sp2C - sp38) * (sp18 - sp24);
        D_8035FFA4[sp46][2] = (sp2C - sp38) * (sp1C - sp28) - (sp28 - sp34) * (sp20 - sp2C);
    }
}

s8 small_float_to_byte(float decimal) // This function converts a decimal to a signed byte by
                                      // multiplying it by 127 or 128 and rounding away from 0.
{
    s8 convertedFloat;

    if (decimal > 0.0) {
        convertedFloat = decimal * 127.0 + 0.5; // round up
    } else if (decimal < 0.0) {
        convertedFloat = decimal * 128.0 - 0.5; // round down
    } else {
        convertedFloat = 0; // don't round 0
    }
    return convertedFloat;
}

void func_802D39DC(s16 *a, s16 b) {
    UNUSED s16 unused;
    s16 sp34;
    s16 index;
    s16 index2;
    s16 sp2E;
    s16 sp2C = 0;

    for (index = 0; index < b; index++) {
        f32 sp28 = 0.0f;
        f32 sp24 = 0.0f;
        f32 sp20 = 0.0f;
        f32 sp1C;

        sp2E = a[sp2C];
        for (index2 = 0; index2 < sp2E; index2++) {
            sp34 = a[sp2C + index2 + 1];
            sp28 += D_8035FFA4[sp34][0];
            sp24 += D_8035FFA4[sp34][1];
            sp20 += D_8035FFA4[sp34][2];
        }
        sp2C += sp2E + 1;
        sp28 /= sp2E;
        sp24 /= sp2E;
        sp20 /= sp2E;
        sp1C = sqrtf(sp28 * sp28 + sp24 * sp24 + sp20 * sp20);
        if (sp1C == 0.0) {
            D_8035FFA0[index].unk6[0] = 0;
            D_8035FFA0[index].unk6[1] = 0;
            D_8035FFA0[index].unk6[2] = 0;
        } else {
            D_8035FFA0[index].unk6[0] = small_float_to_byte(sp28 / sp1C);
            D_8035FFA0[index].unk6[1] = small_float_to_byte(sp24 / sp1C);
            D_8035FFA0[index].unk6[2] = small_float_to_byte(sp20 / sp1C);
        }
    }
}

void *func_802D3CF0(u8 *img, s16 tWidth, s16 tHeight, s16 *d, s16 e, s16 f, u8 g) {
    s16 sp9E;
    s16 sp9C;
    s16 sp9A;
    s16 sp98;
    s16 sp96;
    s16 tx;
    s16 ty;
    s16 sp90 = f / 5;
    s16 sp8E = f % 5;
    s16 sp8C = f * 3;
    s16 sp8A = sp90 * 2 + sp8E + 7;
    Vtx *verts = alloc_display_list(sp8C * sizeof(*verts));
    Gfx *sp80 = alloc_display_list(sp8A * sizeof(*sp80));
    Gfx *sp7C = sp80;

    if (verts == NULL || sp80 == NULL) {
    }

    gDPSetTextureImage(sp7C++, G_IM_FMT_RGBA, G_IM_SIZ_16b, 1, img);
    gDPTileSync(sp7C++);
    gDPSetTile(sp7C++, G_IM_FMT_RGBA, G_IM_SIZ_16b, 0, 0, G_TX_LOADTILE, 0, 
        G_TX_WRAP | G_TX_NOMIRROR, G_TX_NOMASK, G_TX_NOLOD, G_TX_WRAP | G_TX_NOMIRROR, G_TX_NOMASK, G_TX_NOLOD);
    gDPLoadSync(sp7C++);
    gDPLoadBlock(sp7C++, G_TX_LOADTILE, 0, 0, tWidth * tHeight - 1, CALC_DXT(tWidth, G_IM_SIZ_16b_BYTES))

    for (sp9E = 0; sp9E < sp90; sp9E++) {
        sp9A = e * 3 + sp9E * 15 + 2;
        for (sp9C = 0; sp9C < 15; sp9C++) {
            sp98 = d[sp9A + sp9C];
            sp96 = d[sp98 * 3 + 1];
            tx = d[sp98 * 3 + 2];
            ty = d[sp98 * 3 + 3];
            make_vertex(verts, sp9E * 15 + sp9C, D_8035FFA0[sp96].unk0[0], D_8035FFA0[sp96].unk0[1],
                        D_8035FFA0[sp96].unk0[2], tx, ty, D_8035FFA0[sp96].unk6[0],
                        D_8035FFA0[sp96].unk6[1], D_8035FFA0[sp96].unk6[2], g);
        }
        gSPVertex(sp7C++, VIRTUAL_TO_PHYSICAL(verts + sp9E * 15), 15, 0);
        gSPDisplayList(sp7C++, dl_paintings_draw_ripples);
    }

    sp9A = e * 3 + sp90 * 15 + 2;
    for (sp9C = 0; sp9C < sp8E * 3; sp9C++) {
        sp98 = d[sp9A + sp9C];
        sp96 = d[sp98 * 3 + 1];
        tx = d[sp98 * 3 + 2];
        ty = d[sp98 * 3 + 3];
        make_vertex(verts, sp90 * 15 + sp9C, D_8035FFA0[sp96].unk0[0], D_8035FFA0[sp96].unk0[1],
                    D_8035FFA0[sp96].unk0[2], tx, ty, D_8035FFA0[sp96].unk6[0],
                    D_8035FFA0[sp96].unk6[1], D_8035FFA0[sp96].unk6[2], g);
    }
    gSPVertex(sp7C++, VIRTUAL_TO_PHYSICAL(verts + sp90 * 15), sp8E * 3, 0);
    for (sp9E = 0; sp9E < sp8E; sp9E++)
        gSP1Triangle(sp7C++, sp9E * 3, sp9E * 3 + 1, sp9E * 3 + 2, 0);
    gSPEndDisplayList(sp7C);
    return sp80;
}

Gfx *func_802D43FC(struct Painting *painting) {
    float sp4C = painting->vSize / DEFAULT_HEIGHT;
    Mtx *sp48 = alloc_display_list(sizeof(Mtx));
    Mtx *sp44 = alloc_display_list(sizeof(Mtx));
    Mtx *sp40 = alloc_display_list(sizeof(Mtx));
    Mtx *sp3C = alloc_display_list(sizeof(Mtx));
    Gfx *sp38 = alloc_display_list(5 * sizeof(Gfx));
    Gfx *sp34 = sp38;

    if (sp48 == NULL || sp44 == NULL || sp40 == NULL || sp38 == NULL) {
    }

    guTranslate(sp40, painting->vXPos, painting->vYPos, painting->vZPos);
    guRotate(sp48, painting->vXRotation, 1.0f, 0.0f, 0.0f);
    guRotate(sp44, painting->vYRotation, 0.0f, 1.0f, 0.0f);
    guScale(sp3C, sp4C, sp4C, sp4C);

    gSPMatrix(sp34++, sp40, G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_PUSH);
    gSPMatrix(sp34++, sp48, G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH);
    gSPMatrix(sp34++, sp44, G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH);
    gSPMatrix(sp34++, sp3C, G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH);
    gSPEndDisplayList(sp34);

    return sp38;
}

Gfx *func_802D45FC(struct Painting *painting) {
    s16 sp66;
    s16 sp64;
    s16 sp62;
    s16 *sp5C;
    s16 faceCount = painting->faceCount;
    s16 tWidth = painting->textureWidth;
    s16 tHeight = painting->textureHeight;
    s16 **meshArray = segmented_to_virtual(painting->meshData);
    u8 **tArray = segmented_to_virtual(painting->textureArray);
    Gfx *sp48 = alloc_display_list((faceCount + 6) * sizeof(Gfx));
    Gfx *sp44 = sp48;

    if (sp48 == NULL) {
        return sp48;
    }

    gSPDisplayList(sp44++, func_802D43FC(painting));
    gSPDisplayList(sp44++, dl_paintings_rippling_begin);
    gSPDisplayList(sp44++, painting->displayList68);

    for (sp62 = 0; sp62 < faceCount; sp62++) {
        sp5C = segmented_to_virtual(meshArray[sp62]);
        sp66 = sp5C[0];
        sp64 = sp5C[sp66 * 3 + 1];
        gSPDisplayList(sp44++, func_802D3CF0(tArray[sp62], tWidth, tHeight, sp5C, sp66, sp64,
                                             painting->brightness));
    }
    painting_update_ripple_status(painting);
    gSPPopMatrix(sp44++, G_MTX_MODELVIEW);
    gSPDisplayList(sp44++, dl_paintings_rippling_end);
    gSPEndDisplayList(sp44);
    return sp48;
}

Gfx *func_802D4874(struct Painting *painting) {
    s16 sp5E;
    s16 sp5C;
    s16 *sp58;
    s16 tWidth = painting->textureWidth;
    s16 tHeight = painting->textureHeight;
    s16 **meshArray = segmented_to_virtual(painting->meshData);
    u8 **tArray = segmented_to_virtual(painting->textureArray);
    Gfx *sp48 = alloc_display_list(7 * sizeof(Gfx));
    Gfx *sp44 = sp48;

    if (sp48 == NULL) {
        return sp48;
    }

    gSPDisplayList(sp44++, func_802D43FC(painting));
    gSPDisplayList(sp44++, dl_paintings_env_mapped_begin);
    gSPDisplayList(sp44++, painting->displayList68);
    sp58 = segmented_to_virtual(meshArray[0]);
    sp5E = sp58[0];
    sp5C = sp58[sp5E * 3 + 1];
    gSPDisplayList(sp44++,
                   func_802D3CF0(tArray[0], tWidth, tHeight, sp58, sp5E, sp5C, painting->brightness));
    painting_update_ripple_status(painting);
    gSPPopMatrix(sp44++, G_MTX_MODELVIEW);
    gSPDisplayList(sp44++, dl_paintings_env_mapped_end);
    gSPEndDisplayList(sp44);
    return sp48;
}

Gfx *display_painting_rippling(struct Painting *painting) {
    s16 *sp34 = segmented_to_virtual(seg2_triangle_mesh);
    s16 *sp30 = segmented_to_virtual(seg2_mesh_order);
    s16 sp2E = sp34[0];
    s16 sp2C = sp34[sp2E * 3 + 1];
    Gfx *sp28;

    Print1(painting, sp34, sp2E);
    Print2(sp34, sp2E, sp2C);
    func_802D39DC(sp30, sp2E);
    switch (painting->rippleShape) {
        case RIPPLE_SHAPE_WAVE:
            sp28 = func_802D45FC(painting);
            break;
        case RIPPLE_SHAPE_CONCENTRIC:
            sp28 = func_802D4874(painting);
            break;
    }
    mem_pool_free(gEffectsMemoryPool, D_8035FFA0);
    mem_pool_free(gEffectsMemoryPool, D_8035FFA4);
    return sp28;
}

Gfx *display_painting_not_rippling(struct Painting *painting) {
    Gfx *sp2C = alloc_display_list(4 * sizeof(Gfx));
    Gfx *sp28 = sp2C;

    if (sp2C == NULL) {
        return sp2C;
    }
    gSPDisplayList(sp28++, func_802D43FC(painting));
    gSPDisplayList(sp28++, painting->displayList58);
    gSPPopMatrix(sp28++, G_MTX_MODELVIEW);
    gSPEndDisplayList(sp28);
    return sp2C;
}

void reset_painting(struct Painting *painting) {
    painting->lastFloor = 0;
    painting->currFloor = 0;
    painting->floorEntered = 0;
    painting->lastMarioUnderPainting = 0;
    painting->currMarioUnderPainting = 0;
    painting->marioNewlyUnderPainting = 0;
    ripplingPainting = NULL;
#ifndef TARGET_N64
    // Make sure all variables are reset correctly.
    // On N64 the segments that contain the relevant
    // Painting structs are reloaded from ROM upon level load.
    painting->rippleStatus = RIPPLE_STATE_NONE;
    painting->currRippleMag = 0.0f;
    painting->rippleMagMultiplier = 1.0f;
    painting->currRippleRate = 0.0f;
    painting->dispersionFactor = 0.0f;
    painting->rippleTimer = 0.0f;
    painting->horizontalRippleSpot = 0.0f;
    painting->verticalRippleSpot = 0.0f;
    if (painting == &ddd_painting) {
        // Move DDD painting to initial position, in case the animation
        // that moves the painting stops during level unload.
        painting->vXPos = 3456.0f;
    }
#endif
}

void update_ddd_painting(struct Painting *painting, float frontPos, float backPos,
                         float speed) // Tells the DDD painting whether to move back
{
    u32 dddFlags = save_file_get_star_flags(gCurrSaveFileNum - 1,
                                            DIRE_DIRE_DOCKS - 1); // Obtain the DDD star flags
    u32 saveFileFlags = save_file_get_flags();                    // Get the other save file flags
    u32 bowsersSubBeaten =
        dddFlags & BOARD_BOWSERS_SUB; // Find out whether Board Bowser's Sub was collected
    u32 dddBack = saveFileFlags & SAVE_FLAG_DDD_MOVED_BACK; // Check whether DDD has already moved back

    if (!bowsersSubBeaten && !dddBack) {
        painting->vXPos = frontPos; // If we haven't collected the star or moved the painting, put the
                                    // painting at the front
        dddStatus = 0;
    } else if (bowsersSubBeaten
               && !dddBack) // If we've collected the star but not moved the painting back,
    {
        painting->vXPos +=
            speed; // Each frame, move the painting by a certain speed towards the back area.
        dddStatus = BOWSERS_SUB_BEATEN;
        if (painting->vXPos >= backPos) {
            painting->vXPos = backPos;
            save_file_set_flags(
                SAVE_FLAG_DDD_MOVED_BACK); // Tell the save file that we've moved DDD back.
        }
    } else if (bowsersSubBeaten && dddBack) {
        painting->vXPos =
            backPos; // If the painting has already moved back, place it in the back position.
        dddStatus = BOWSERS_SUB_BEATEN | DDD_BACK;
    }
}

struct Struct802D4E04 {
    u8 filler0[2];
    s16 unk2;
    u8 filler4[20];
    u32 unk18; // the upper half is the painting's id
};

void func_802D4E04(struct GraphNodeGenerated *a, struct Painting *b) {
    switch (b->brightness) {
        case 0xFF: // brightest
            a->fnNode.node.flags = (a->fnNode.node.flags & 0xFF) | 0x100;
            break;
        default:
            a->fnNode.node.flags = (a->fnNode.node.flags & 0xFF) | 0x500;
            break;
    }
}

Gfx *display_painting(struct Painting *painting) {
    switch (painting->rippleStatus) {
        case RIPPLE_STATE_NONE:
            return display_painting_not_rippling(painting);
            break;
        default:
            return display_painting_rippling(painting);
            break;
    }
}

void vertical_painting_ripple(struct Painting *painting, struct Painting *paintingGroup[]) {
    if (painting->rippleTrigger
        == RIPPLE_TRIGGER_PROXIMITY) // make the painting ripple using a different function based on its
                                     // ripple trigger and status
    {
        switch (painting->rippleStatus) {
            case RIPPLE_STATE_NONE:
                vertical_proximity_ripple_painting_ripple(painting, paintingGroup);
                break;
            case RIPPLE_STATE_IDLE:
                vertical_proximity_ripple_painting_ripple_if_mario_enters(painting, paintingGroup);
                break;
        }
    } else if (painting->rippleTrigger == RIPPLE_TRIGGER_CONTINUOUS) {
        switch (painting->rippleStatus) {
            case RIPPLE_STATE_NONE:
                vertical_continuous_ripple_painting_ripple(painting, paintingGroup);
                break;
            case RIPPLE_STATE_IDLE:
                vertical_continuous_ripple_painting_ripple_if_mario_enters(painting, paintingGroup);
                break;
        }
    }
}

void horizontal_painting_ripple(struct Painting *painting, struct Painting *paintingGroup[]) {
    if (painting->rippleTrigger
        == RIPPLE_TRIGGER_PROXIMITY) // make the painting ripple using a different function based on its
                                     // ripple trigger and status
    {
        switch (painting->rippleStatus) // No horizontal proximity ripple paintings exist in-game.
        {
            case RIPPLE_STATE_NONE:
                horizontal_proximity_ripple_painting_ripple(painting, paintingGroup);
                break;
            case RIPPLE_STATE_IDLE:
                horizontal_proximity_ripple_painting_ripple_if_mario_enters(painting, paintingGroup);
                break;
        }
    } else if (painting->rippleTrigger == RIPPLE_TRIGGER_CONTINUOUS) {
        switch (painting->rippleStatus) {
            case RIPPLE_STATE_NONE:
                horizontal_continuous_ripple_painting_ripple(painting, paintingGroup);
                break;
            case RIPPLE_STATE_IDLE:
                horizontal_continuous_ripple_painting_ripple_if_mario_enters(painting, paintingGroup);
                break;
        }
    }
}

Gfx *Geo18_802D5B98(s32 run, struct GraphNode *node, UNUSED void *context) {
    struct GraphNodeGenerated *sp2C = (struct GraphNodeGenerated *) node;
    s32 sp28 = (sp2C->parameter >> 8) & 0xFF;
    s32 id = sp2C->parameter & 0xFF;
    Gfx *sp20 = NULL;
    struct Painting **paintingGroup = paintingGroups[sp28];
    struct Painting *painting = segmented_to_virtual(paintingGroup[id]);

    if (run != TRUE) {
        reset_painting(painting);
    } else if (run == TRUE) // because the extra comparison was really necessary...
    {
        if (sp28 == 1 && id == PAINTING_ID_DDD) { // painting is DDD painting
            update_ddd_painting(painting, 3456.0f, 5529.6f, 20.0f);
        }
        func_802D4E04(sp2C, painting);
        sp20 = display_painting(painting);
        painting_update_floors(painting);
        switch ((s16) painting->vXRotation) {
            case ROTATION_VERTICAL:
                vertical_painting_ripple(painting, paintingGroup);
                break;
            default:
                horizontal_painting_ripple(painting, paintingGroup);
                break;
        }
    }
    return sp20;
}

Gfx *Geo18_802D5D0C(s32 run, UNUSED struct GraphNode *node, UNUSED f32 c[4][4]) {
    struct Surface *surface;

    if (run != TRUE) {
        gLastPaintingUpdateCounter = gAreaUpdateCounter - 1;
        gPaintingUpdateCounter = gAreaUpdateCounter;
    } else {
        gLastPaintingUpdateCounter = gPaintingUpdateCounter;
        gPaintingUpdateCounter = gAreaUpdateCounter;
        find_floor(gMarioObject->oPosX, gMarioObject->oPosY, gMarioObject->oPosZ, &surface);
        gPaintingMarioFloorType = surface->type;
        gPaintingMarioXPos = gMarioObject->oPosX;
        gPaintingMarioYPos = gMarioObject->oPosY;
        gPaintingMarioZPos = gMarioObject->oPosZ;
    }
    return NULL;
}
