#include "Library/LiveActor/ActorCollisionFunction.h"

#include "Library/Base/StringUtil.h"
#include "Library/Collision/Collider.h"
#include "Library/Collision/CollisionDirector.h"
#include "Library/Collision/CollisionParts.h"
#include "Library/Collision/CollisionPartsKeeperUtil.h"
#include "Library/Collision/CollisionPartsTriangle.h"
#include "Library/Collision/ICollisionPartsKeeper.h"
#include "Library/Collision/KCollisionServer.h"
#include "Library/LiveActor/ActorMovementFunction.h"
#include "Library/LiveActor/ActorPoseUtil.h"
#include "Library/LiveActor/LiveActor.h"
#include "Library/Math/MathUtil.h"
#include "Project/Collision/ActorCollisionController.h"

namespace al {

Collider* getActorCollider(const LiveActor* actor) {
    return actor->getCollider();
}

bool isExistActorCollider(const LiveActor* actor) {
    return actor->getCollider() != nullptr;
}

const HitSensor* getActorCollisionPartsSensor(const LiveActor* actor) {
    return actor->getCollisionParts()->getConnectedSensor();
}

bool isExistCollisionParts(const LiveActor* actor) {
    return actor->getCollisionParts() != nullptr;
}

bool isEqualCollisionParts(const LiveActor* actor, const CollisionParts* collisionParts) {
    return collisionParts && actor->getCollisionParts() == collisionParts;
}

void validateCollisionParts(LiveActor* actor) {
    CollisionParts* collisionParts = actor->getCollisionParts();
    if (collisionParts->getSyncCollisonMtx()) {
        collisionParts->resetAllMtx();
    } else {
        sead::Matrix34f mtx;
        makeMtxSRT(&mtx, actor);
        collisionParts->resetAllMtx(mtx);
    }
    collisionParts->validateByUser();
}

void invalidateCollisionParts(LiveActor* actor) {
    actor->getCollisionParts()->invalidateByUser();
}

void validateCollisionPartsBySystem(LiveActor* actor) {
    CollisionParts* collisionParts = actor->getCollisionParts();
    collisionParts->validateBySystem();

    if (collisionParts->getSyncCollisonMtx()) {
        collisionParts->resetAllMtx();
    } else {
        sead::Matrix34f mtx;
        makeMtxSRT(&mtx, actor);
        collisionParts->resetAllMtx(mtx);
    }
}

void invalidateCollisionPartsBySystem(LiveActor* actor) {
    actor->getCollisionParts()->invalidateBySystem();
}

bool isValidCollisionParts(const LiveActor* actor) {
    return actor->getCollisionParts()->isValidCollision();
}

void setCollisionPartsSpecialPurposeName(LiveActor* actor, const char* name) {
    actor->getCollisionParts()->setSpecialPurpose(name);
}

void resetAllCollisionMtx(LiveActor* actor) {
    CollisionParts* collisionParts = actor->getCollisionParts();

    if (collisionParts->getSyncCollisonMtx()) {
        collisionParts->resetAllMtx();
    } else {
        sead::Matrix34f mtx;
        makeMtxSRT(&mtx, actor);
        collisionParts->resetAllMtx(mtx);
    }

    alCollisionUtil::getCollisionPartsKeeper(actor)->resetToCollisionPartsList(collisionParts);
}

void syncCollisionMtx(LiveActor* actor, const sead::Matrix34f* mtx) {
    syncCollisionMtx(actor, actor->getCollisionParts(), mtx);
}

void syncCollisionMtx(LiveActor* actor, CollisionParts* collisionParts,
                      const sead::Matrix34f* mtx) {
    if (!collisionParts->isValidCollision())
        return;

    if (collisionParts->getSyncCollisonMtx()) {
        collisionParts->syncMtx();
    } else if (mtx) {
        collisionParts->syncMtx(*mtx);
    } else {
        sead::Matrix34f newMtx;
        makeMtxSRT(&newMtx, actor);
        collisionParts->syncMtx(newMtx);
    }
}

void setSyncCollisionMtxPtr(LiveActor* actor, const sead::Matrix34f* mtx) {
    actor->getCollisionParts()->setSyncCollisionMtx(mtx);
}

bool isOnGround(const LiveActor* actor, u32 coyoteTime) {
    Collider* collider = actor->getCollider();
    if (!isCollidedGround(actor) && collider->get_264() > coyoteTime)
        return false;
    return !(getVelocity(actor).dot(collider->getRecentOnGroundNormal(coyoteTime)) > 0.0f);
}

bool isOnGroundFace(const LiveActor* actor) {
    return isOnGround(actor, 0) && !isCollidedGroundEdgeOrCorner(actor);
}

bool isCollidedGroundEdgeOrCorner(const LiveActor* actor) {
    return isCollidedGround(actor) && !actor->getCollider()->getFloorHit().isCollisionAtFace();
}

bool isOnGroundNoVelocity(const LiveActor* actor, u32 coyoteTime) {
    if (!isExistActorCollider(actor))
        return getTrans(actor).y <= 0.0f;

    if (isCollidedGround(actor))
        return true;

    return actor->getCollider()->get_264() <= coyoteTime;
}

bool isOnGroundDegree(const LiveActor* actor, f32 angle, u32 coyoteTime) {
    Collider* collider = actor->getCollider();
    if (!(isCollidedGround(actor) || collider->get_264() <= coyoteTime))
        return false;

    if (sead::Mathf::abs((*collider->get_30()).dot(collider->getRecentOnGroundNormal(coyoteTime))) <
        sead::Mathf::cos(sead::Mathf::deg2rad(angle)))
        return false;

    return !(getVelocity(actor).dot(collider->getRecentOnGroundNormal(coyoteTime)) > 0.0f);
}

bool isOnGroundFaceDegree(const LiveActor* actor, f32 angle) {
    return isOnGroundDegree(actor, angle, 0) && !isCollidedGroundEdgeOrCorner(actor);
}

bool isOnGroundNoVelocityDegree(const LiveActor* actor, f32 angle, u32 coyoteTime) {
    Collider* collider = actor->getCollider();
    if (!collider)
        return getTrans(actor).y <= 0.0;

    if (!(isCollidedGround(actor) || collider->get_264() <= coyoteTime))
        return false;

    if (sead::Mathf::abs((*collider->get_30()).dot(collider->getRecentOnGroundNormal(coyoteTime))) <
        sead::Mathf::cos(sead::Mathf::deg2rad(angle)))
        return false;

    return true;
}

const sead::Vector3f& getOnGroundNormal(const LiveActor* actor, u32 offset) {
    return actor->getCollider()->getRecentOnGroundNormal(offset);
}

void setColliderRadius(LiveActor* actor, f32 radius) {
    actor->getCollider()->setRadius(radius);
}

void setColliderOffsetY(LiveActor* actor, f32 offsety) {
    actor->getCollider()->setOffsetY(offsety);
}

f32 getColliderRadius(const LiveActor* actor) {
    return actor->getCollider()->getRadius();
}

f32 getColliderOffsetY(const LiveActor* actor) {
    return actor->getCollider()->getOffsetY();
}

const sead::Vector3f& getColliderFixReaction(const LiveActor* actor) {
    return actor->getCollider()->getFixReaction();
}

void calcColliderPos(sead::Vector3f* outPos, const LiveActor* actor) {
    actor->getCollider()->calcCheckPos(outPos);
}

void validateColliderRobustCheck(LiveActor* actor) {
    actor->getCollider()->validateRobustCheck();
}

void invalidateColliderRobustCheck(LiveActor* actor) {
    actor->getCollider()->invalidateRobustCheck();
}

void setColliderReactMovePower(LiveActor* actor, bool isEnabled) {
    actor->getCollider()->setReactMovePower(isEnabled);
}

void calcColliderFloorRotatePower(sead::Quatf* outRotatePower, LiveActor* actor) {
    actor->getCollider()->getFloorHit().triangle.calcForceRotatePower(outRotatePower);
}

void calcJumpInertia(sead::Vector3f* outJumpInertia, LiveActor* actor,
                     const sead::Vector3f& velocityDir, f32 force) {
    if (!isCollidedGround(actor)) {
        outJumpInertia->set({0.0f, 0.0f, 0.0f});
        return;
    }

    sead::Vector3f movePower = {0.0f, 0.0f, 0.0f};
    const HitInfo& floorHit = actor->getCollider()->getFloorHit();
    if (floorHit.triangle.getCollisionParts() && floorHit.triangle.isHostMoved())
        floorHit.triangle.calcForceMovePower(&movePower, getTrans(actor));

    verticalizeVec(&movePower, getGravity(actor), movePower);

    sead::Vector3f hMoveDir = movePower;
    tryNormalizeOrZero(&hMoveDir);

    sead::Vector3f hVelDir = {0.0f, 0.0f, 0.0f};
    verticalizeVec(&hVelDir, getGravity(actor), velocityDir);
    if (!tryNormalizeOrZero(&hVelDir)) {
        outJumpInertia->set(movePower * force);
        return;
    }

    force *= sead::Mathf::clampMin(hMoveDir.dot(hVelDir), 0.0f);
    outJumpInertia->set(movePower * force);
}

bool isCollidedGround(const LiveActor* actor) {
    return actor->getCollider()->get_110() >= 0.0f;
}

void calcJumpInertiaWall(sead::Vector3f* outJumpInertia, LiveActor* actor, f32 force) {
    if (!isCollidedWall(actor)) {
        outJumpInertia->set({0.0f, 0.0f, 0.0f});
        return;
    }

    sead::Vector3f movePower = {0.0f, 0.0f, 0.0f};
    const sead::Vector3f& trans = getTrans(actor);
    const HitInfo& floorHit = actor->getCollider()->getWallHit();

    if (floorHit.triangle.getCollisionParts() && alCollisionUtil::isCollisionMoving(&floorHit))
        floorHit.triangle.calcForceMovePower(&movePower, trans);

    verticalizeVec(&movePower, getGravity(actor), movePower);

    outJumpInertia->set(movePower * force);
}

bool isCollidedWall(const LiveActor* actor) {
    return actor->getCollider()->get_1b8() >= 0.0f;
}

// TODO: probably a sead function?
inline f32 normalize2(sead::Vector3f* v, f32 scalar) {
    const f32 len = v->length();
    if (len > 0) {
        const f32 inv_len = scalar / len;
        v->x *= inv_len;
        v->y *= inv_len;
        v->z *= inv_len;
    }

    return len;
}

void scaleVelocityInertiaWallHit(LiveActor* actor, f32 velocityScaleH, f32 maxVelocityH,
                                 f32 maxScaledVelocityH) {
    if (!isCollidedWall(actor))
        return;

    sead::Vector3f faceNormal = getCollidedWallNormal(actor);
    verticalizeVec(&faceNormal, getGravity(actor), faceNormal);
    if (!tryNormalizeOrZero(&faceNormal))
        return;

    sead::Vector3f jumpInertia = {0.0f, 0.0f, 0.0f};
    calcJumpInertiaWall(&jumpInertia, actor, 1.0f);

    sead::Vector3f velocityH = {0.0f, 0.0f, 0.0f};
    sead::Vector3f velocityV = {0.0f, 0.0f, 0.0f};
    separateVelocityHV(&velocityH, &velocityV, actor);

    sead::Vector3f velocityDirH = {0.0f, 0.0f, 0.0f};
    tryNormalizeOrZero(&velocityDirH, velocityH);

    if (velocityDirH.dot(faceNormal) < -0.5f) {
        f32 speedH = velocityH.length();
        f32 scaledVelocity = speedH * velocityScaleH;
        f32 newSpeedH = sead::Mathf::clamp(scaledVelocity, sead::Mathf::min(speedH, maxVelocityH),
                                           maxScaledVelocityH);

        normalize2(&velocityH, newSpeedH);

        f32 newVelocityH = jumpInertia.dot(velocityDirH);
        if (-newVelocityH < faceNormal.dot(velocityDirH))
            normalize2(&velocityH, newVelocityH);
    }

    setVelocity(actor, velocityH + velocityV);
}

const sead::Vector3f& getCollidedWallNormal(const LiveActor* actor) {
    return actor->getCollider()->getWallHit().triangle.getFaceNormal();
}

void calcCollidedNormalSum(const LiveActor* actor, sead::Vector3f* outNormal) {
    outNormal->set({0.0f, 0.0f, 0.0f});
    Collider* collider = actor->getCollider();

    if (collider->get_110() >= 0.0f)
        outNormal->add(collider->getFloorHit().triangle.getFaceNormal());

    if (collider->get_1b8() >= 0.0f)
        outNormal->add(collider->getWallHit().triangle.getFaceNormal());

    if (collider->get_260() >= 0.0f)
        outNormal->add(collider->getCeilingHit().triangle.getFaceNormal());

    *outNormal *= 1.0f / 3.0f;
}

void calcGroundNormalOrUpDir(sead::Vector3f* outDir, const LiveActor* actor) {
    if (isCollidedGround(actor))
        *outDir = getCollidedGroundNormal(actor);
    else
        calcUpDir(outDir, actor);
}

const sead::Vector3f& getCollidedGroundNormal(const LiveActor* actor) {
    return actor->getCollider()->getFloorHit().triangle.getFaceNormal();
}

void calcGroundNormalOrGravityDir(sead::Vector3f* outDir, const LiveActor* actor) {
    if (isCollidedGround(actor))
        *outDir = getCollidedGroundNormal(actor);
    else
        *outDir = -getGravity(actor);
}

void setColliderFilterTriangle(LiveActor* actor, const TriangleFilterBase* triangleFilter) {
    actor->getCollider()->setTriangleFilter(triangleFilter);
}

void setColliderFilterCollisionParts(LiveActor* actor,
                                     const CollisionPartsFilterBase* collisionPartsFilter) {
    actor->getCollider()->setCollisionPartsFilter(collisionPartsFilter);
}

void createAndSetColliderFilterExistActor(LiveActor* actor) {
    createAndSetColliderFilterExistActor(actor, actor);
}

void createAndSetColliderFilterExistActor(LiveActor* actor, LiveActor* filterActor) {
    CollisionPartsFilterActor* collisionPartsFilter = new CollisionPartsFilterActor(filterActor);
    setColliderFilterCollisionParts(actor, collisionPartsFilter);
}

void createAndSetColliderFilterSubActor(LiveActor* actor) {
    CollisionPartsFilterSubActor* collisionPartsFilter = new CollisionPartsFilterSubActor(actor);
    setColliderFilterCollisionParts(actor, collisionPartsFilter);
}

void createAndSetColliderSpecialPurpose(LiveActor* actor, const char* name) {
    CollisionPartsFilterSpecialPurpose* collisionPartsFilter =
        new CollisionPartsFilterSpecialPurpose(name);
    setColliderFilterCollisionParts(actor, collisionPartsFilter);
}

void createAndSetColliderSpecialPurposeForCollisionActor(LiveActor* actor, const char* name) {
    CollisionPartsFilterMergePair* collisionPartsFilter = new CollisionPartsFilterMergePair(
        new CollisionPartsFilterSpecialPurpose(name), new CollisionPartsFilterActor(actor));
    setColliderFilterCollisionParts(actor, collisionPartsFilter);
}

void createAndSetColliderIgnoreOptionalPurpose(LiveActor* actor, const char* name) {
    CollisionPartsFilterIgnoreOptionalPurpose* collisionPartsFilter =
        new CollisionPartsFilterIgnoreOptionalPurpose(name);
    setColliderFilterCollisionParts(actor, collisionPartsFilter);
}

void createAndSetColliderSpecialAndIgnoreOptionalPurpose(LiveActor* actor, const char* name,
                                                         const char* ignoreName) {
    CollisionPartsFilterMergePair* collisionPartsFilter = new CollisionPartsFilterMergePair(
        new CollisionPartsFilterSpecialPurpose(name),
        new CollisionPartsFilterIgnoreOptionalPurpose(ignoreName));
    setColliderFilterCollisionParts(actor, collisionPartsFilter);
}

ActorCollisionController* createActorCollisionController(LiveActor* actor) {
    return new ActorCollisionController(actor);
}

void setColliderRadius(ActorCollisionController* collisionController, f32 radius) {
    collisionController->setColliderRadius(radius);
}

void setColliderRadiusScale(ActorCollisionController* collisionController, f32 scale) {
    collisionController->setColliderRadiusScale(scale);
}

void setColliderOffsetY(ActorCollisionController* collisionController, f32 offsetY) {
    collisionController->setColliderOffsetY(offsetY);
}

void resetActorCollisionController(ActorCollisionController* collisionController, s32 delay) {
    collisionController->resetToOrigin(delay);
}

void updateActorCollisionController(ActorCollisionController* collisionController) {
    collisionController->update();
}

const char* getCollidedFloorMaterialCodeName(const LiveActor* actor) {
    return getCollidedFloorMaterialCodeName(actor->getCollider());
}

const char* getCollidedFloorMaterialCodeName(const Collider* collider) {
    return getMaterialCodeName(collider->getFloorHit().triangle);
}

const char* getCollidedWallMaterialCodeName(const LiveActor* actor) {
    return getMaterialCodeName(actor->getCollider()->getWallHit().triangle);
}

const char* getCollidedCeilingMaterialCodeName(const LiveActor* actor) {
    return getMaterialCodeName(actor->getCollider()->getCeilingHit().triangle);
}

bool isCollidedFloorCode(const LiveActor* actor, const char* name) {
    return isCollidedCollisionCode(actor, "FloorCode", name);
}

bool isCollidedCollisionCode(const LiveActor* actor, const char* sensorName, const char* name) {
    Collider* collider = actor->getCollider();
    if (collider->get_48() == 0) {
        if (collider->get_110() >= 0.0f) {
            if (isEqualString(name,
                              getCollisionCodeName(collider->getFloorHit().triangle, sensorName))) {
                return true;
            }
        }
        if (collider->get_1b8() >= 0.0f) {
            if (isEqualString(name,
                              getCollisionCodeName(collider->getWallHit().triangle, sensorName))) {
                return true;
            }
        }
        if (collider->get_260() >= 0.0f) {
            if (isEqualString(
                    name, getCollisionCodeName(collider->getCeilingHit().triangle, sensorName))) {
                return true;
            }
        }
        return false;
    }

    u32 size = collider->getPlaneCount();
    for (u32 i = 0; i < size; i++)
        if (isEqualString(name, getCollisionCodeName(*collider->getPlane(i), sensorName)))
            return true;

    return false;
}

bool isCollidedWallCode(const LiveActor* actor, const char* name) {
    return isCollidedCollisionCode(actor, "WallCode", name);
}

bool isCollidedCameraCode(const LiveActor* actor, const char* name) {
    return isCollidedCollisionCode(actor, "CameraCode", name);
}

bool isCollidedMaterialCode(const LiveActor* actor, const char* name) {
    return isCollidedCollisionCode(actor, "MaterialCode", name);
}

bool isCollidedGroundFloorCode(const LiveActor* actor, const char* name) {
    return isCollidedGroundCollisionCode(actor, "FloorCode", name);
}

bool isCollidedGroundCollisionCode(const LiveActor* actor, const char* sensorName,
                                   const char* name) {
    if (!isCollidedGround(actor))
        return false;

    return isEqualString(
        name, getCollisionCodeName(actor->getCollider()->getFloorHit().triangle, sensorName));
}

bool isCollided(const LiveActor* actor) {
    return isCollidedGround(actor) || isCollidedWall(actor) || isCollidedCeiling(actor);
}

bool isCollidedCeiling(const LiveActor* actor) {
    return actor->getCollider()->get_260() >= 0.0f;
}

bool isCollidedWallFace(const LiveActor* actor) {
    if (!isCollidedWall(actor))
        return false;

    return actor->getCollider()->isCollidedWallFace();
}

bool isCollidedVelocity(const LiveActor* actor) {
    if (isOnGround(actor, 0))
        return true;

    return isCollidedWallVelocity(actor) || isCollidedCeilingVelocity(actor);
}

bool isCollidedWallVelocity(const LiveActor* actor) {
    return isCollidedWall(actor) && getVelocity(actor).dot(getCollidedWallNormal(actor)) < 0.0f;
}

bool isCollidedCeilingVelocity(const LiveActor* actor) {
    return isCollidedCeiling(actor) &&
           getVelocity(actor).dot(getCollidedCeilingNormal(actor)) < 0.0f;
}

const sead::Vector3f& getCollidedCeilingNormal(const LiveActor* actor) {
    return actor->getCollider()->getCeilingHit().triangle.getFaceNormal();
}

const sead::Vector3f& getCollidedGroundPos(const LiveActor* actor) {
    return actor->getCollider()->getFloorHit().collisionHitPos;
}

const sead::Vector3f& getCollidedWallPos(const LiveActor* actor) {
    return actor->getCollider()->getWallHit().collisionHitPos;
}

const sead::Vector3f& getCollidedCeilingPos(const LiveActor* actor) {
    return actor->getCollider()->getCeilingHit().collisionHitPos;
}

f32 calcSpeedCollideWall(const LiveActor* actor) {
    if (!isCollidedWall(actor))
        return 0.0f;
    sead::Vector3f wallNormal = getCollidedWallNormal(actor);
    return -sead::Mathf::min(wallNormal.dot(getVelocity(actor)), 0.0f);
}

f32 calcSpeedCollideGround(const LiveActor* actor) {
    if (!isCollidedGround(actor))
        return 0.0f;
    sead::Vector3f groundNormal = getCollidedGroundNormal(actor);
    return -sead::Mathf::min(groundNormal.dot(getVelocity(actor)), 0.0f);
}

f32 calcSpeedCollideCeiling(const LiveActor* actor) {
    if (!isCollidedCeiling(actor))
        return 0.0f;
    sead::Vector3f ceilingNormal = getCollidedCeilingNormal(actor);
    return -sead::Mathf::min(ceilingNormal.dot(getVelocity(actor)), 0.0f);
}

const CollisionParts* getCollidedGroundCollisionParts(const LiveActor* actor) {
    return tryGetCollidedGroundCollisionParts(actor);
}

const CollisionParts* tryGetCollidedGroundCollisionParts(const LiveActor* actor) {
    if (!isCollidedGround(actor))
        return nullptr;
    return actor->getCollider()->getFloorHit().triangle.getCollisionParts();
}

const CollisionParts* getCollidedWallCollisionParts(const LiveActor* actor) {
    return tryGetCollidedWallCollisionParts(actor);
}

const CollisionParts* tryGetCollidedWallCollisionParts(const LiveActor* actor) {
    if (!isCollidedWall(actor))
        return nullptr;
    return actor->getCollider()->getWallHit().triangle.getCollisionParts();
}

const CollisionParts* getCollidedCeilingCollisionParts(const LiveActor* actor) {
    return tryGetCollidedCeilingCollisionParts(actor);
}

const CollisionParts* tryGetCollidedCeilingCollisionParts(const LiveActor* actor) {
    if (!isCollidedCeiling(actor))
        return nullptr;
    return actor->getCollider()->getCeilingHit().triangle.getCollisionParts();
}

HitSensor* getCollidedGroundSensor(const LiveActor* actor) {
    return tryGetCollidedGroundSensor(actor);
}

HitSensor* tryGetCollidedGroundSensor(const LiveActor* actor) {
    if (!isCollidedGround(actor))
        return nullptr;
    return actor->getCollider()->getFloorHit().triangle.getCollisionParts()->getConnectedSensor();
}

HitSensor* getCollidedWallSensor(const LiveActor* actor) {
    return tryGetCollidedWallSensor(actor);
}

HitSensor* tryGetCollidedWallSensor(const LiveActor* actor) {
    if (!isCollidedWall(actor))
        return nullptr;
    return actor->getCollider()->getWallHit().triangle.getCollisionParts()->getConnectedSensor();
}

HitSensor* getCollidedCeilingSensor(const LiveActor* actor) {
    return tryGetCollidedCeilingSensor(actor);
}

HitSensor* tryGetCollidedCeilingSensor(const LiveActor* actor) {
    if (!isCollidedCeiling(actor))
        return nullptr;
    return actor->getCollider()->getCeilingHit().triangle.getCollisionParts()->getConnectedSensor();
}

HitSensor* tryGetCollidedSensor(const LiveActor* actor) {
    return tryGetCollidedWallSensor(actor)       ?:
               tryGetCollidedGroundSensor(actor) ?:
                                                   tryGetCollidedCeilingSensor(actor);
}

bool tryGetCollidedPos(sead::Vector3f* outPos, const LiveActor* actor) {
    if (isCollidedWall(actor)) {
        outPos->set(getCollidedWallPos(actor));
        return true;
    }
    if (isCollidedGround(actor)) {
        outPos->set(getCollidedGroundPos(actor));
        return true;
    }
    if (isCollidedCeiling(actor)) {
        outPos->set(getCollidedCeilingPos(actor));
        return true;
    }
    return false;
}

void setForceCollisionScaleOne(const LiveActor* actor) {
    actor->getCollisionParts()->setForceCollisionScaleOne();
}

void followRotateFrontAxisUpGround(LiveActor* actor) {
    const CollisionParts* collisionParts = tryGetCollidedGroundCollisionParts(actor);

    if (collisionParts)
        followRotateFrontAxisUp(actor, collisionParts);
}

void followRotateFrontAxisUp(LiveActor* actor, const CollisionParts* collisionParts) {
    sead::Vector3f frontDir = {0.0f, 0.0f, 0.0f};
    calcFrontDir(&frontDir, actor);

    sead::Vector3f sideDir = frontDir;
    sideDir.rotate(collisionParts->getPrevBaseInvMtx());
    sideDir.rotate(collisionParts->getBaseMtx());

    sead::Vector3f upDir = {0.0f, 0.0f, 0.0f};
    calcUpDir(&upDir, actor);

    sead::Vector3f newFrontDir = {0.0f, 0.0f, 0.0f};
    turnVecToVecCosOnPlane(&newFrontDir, frontDir, sideDir, upDir, -1.0f);

    sead::Quatf quat = sead::Quatf::unit;
    makeQuatUpFront(&quat, upDir, newFrontDir);
    updatePoseQuat(actor, quat);
}

}  // namespace al
