#include "d/d_camera.h" #include "c/c_math.h" #include "common.h" #include "d/a/d_a_player.h" #include "d/col/bg/d_bg_s_gnd_chk.h" #include "d/col/bg/d_bg_s_lin_chk.h" #include "d/col/bg/d_bg_s_roof_chk.h" #include "d/d_gfx.h" #include "d/d_pad.h" #include "d/d_sc_game.h" #include "d/d_stage_mgr.h" #include "d/snd/d_snd_3d_manager.h" #include "egg/gfx/eggFrustum.h" #include "egg/math/eggMatrix.h" #include "f/f_base.h" #include "f/f_profile.h" #include "m/m_angle.h" #include "m/m_mtx.h" #include "m/m_vec.h" #include "nw4r/g3d/g3d_camera.h" #include "toBeSorted/d_camera_base.h" #include "toBeSorted/d_camera_math.h" #include "toBeSorted/event_manager.h" class dCamera_HIO_c { public: dCamera_HIO_c(); virtual ~dCamera_HIO_c(); /* 0x04 */ f32 field_0x04; /* 0x08 */ f32 field_0x08; /* 0x0C */ f32 field_0x0C; /* 0x10 */ s16 field_0x10; /* 0x12 */ s16 field_0x12; /* 0x14 */ f32 field_0x14; /* 0x18 */ u16 field_0x18; /* 0x1C */ f32 field_0x1C; /* 0x20 */ f32 field_0x20; /* 0x24 */ s16 field_0x24; /* 0x28 */ f32 field_0x28; /* 0x2C */ f32 field_0x2C; /* 0x30 */ s16 field_0x30; }; dCamera_HIO_c::~dCamera_HIO_c() {} dCamera_HIO_c::dCamera_HIO_c() { field_0x04 = 1.0f; field_0x08 = 240000.0f; field_0x14 = 0.1f; field_0x18 = 0x1555; field_0x1C = 30.0f; field_0x20 = 30.0f; field_0x24 = 8; field_0x28 = 0.7f; field_0x2C = 0.2f; field_0x30 = 0; field_0x0C = 1.0f; field_0x10 = 1; field_0x12 = 1; } static dCamera_HIO_c sHio; SPECIAL_BASE_PROFILE(CAMERA, dCamera_c, fProfile::CAMERA, 0xA, 0x30); void debugWarn2(const char *fmt, ...); void debugPrintf7(const char *fmt, ...); dCamera_c::dCamera_c() : mMyCameraIndex(0), mpScreen(dStageMgr_c::GetInstance()->getScreen(0)), mYAngle(0), mXZAngle(0), field_0x1F8(0), field_0x1FC(0), mFlags(0) { mScreenShaker.init(this); field_0xDC0.setCamera(this); field_0x08C = mVec3_c(0.0f, 1.0f, 0.0f); mActiveCameraIdx = 0; for (int i = 0; i < CAM_MAX; i++) { mpCameras[i] = nullptr; } } int dCamera_c::create() { mFlags = 0; mMyCameraIndex = getFromParams(0, 0xF); dScGame_c::setCamera(mMyCameraIndex, nullptr); mpCameras[CAM_GAME_0] = &mGameCam1; mpCameras[CAM_GAME_1] = &mGameCam2; mpCameras[CAM_EVENT] = &mEventCam; mpCameras[CAM_MAP] = &mMapCam; for (int i = 0; i < CAM_MAX; i++) { if (mpCameras[i] == nullptr) { continue; } if (!mpCameras[i]->doCreate(i)) { return NOT_READY; } } debugWarn2("u can NOT use debug camera!!"); // aww mActiveCameraIdx = 0; mpCameras[0]->activate(); mLetterboxAmount = getEventLetterboxAmount(); field_0x290 = 1.0f; onFlag(CAM_FLAGS_IN_EVENT); // TODO maybe struct + inline mGlobalAlpha = 1.0f; field_0x298 = field_0x299 = 0; field_0x29C = 1.0f; field_0x2A0 = field_0x2A1 = 0; mView1 = mpCameras[mActiveCameraIdx]->getView(); field_0xDCC = false; mYAngle = 0; mXZAngle = 0; dScGame_c::setCamera(mMyCameraIndex, this); updateView(); apply(); const STIF *stif = dStageMgr_c::GetInstance()->getStif(); if (stif != nullptr) { sHio.field_0x04 = stif->field_0x00; sHio.field_0x08 = stif->field_0x04; } field_0x1FC = 0; if ((dScGame_c::isCurrentStage("F000") && 26 <= dScGame_c::currentSpawnInfo.layer && dScGame_c::currentSpawnInfo.layer <= 28) || dScGame_c::isInCredits() || dScGame_c::isSeekerStoneStageAndLayer()) { onFlag(CAM_FLAGS_NO_LETTERBOX_IN_EVENT); dScGame_c::GetInstance()->setTargetingScreenLetterboxAmount(0.0f); } debugPrintf7("create ok!!"); return SUCCEEDED; } // Maybe inlines that couldn't be inlined due to variadics... void debugWarn2(const char *fmt, ...) { // no-op } void debugPrintf7(const char *fmt, ...) { // no-op } int dCamera_c::doDelete() { debugPrintf7("delete!!"); for (int i = 0; i < CAM_MAX; i++) { // No null check needed :) mpCameras[i]->doRemove(); } dScGame_c::setCamera(mMyCameraIndex, nullptr); return SUCCEEDED; } void dCamera_c::setWorldOffset(f32 x, f32 z) { mView.mPosition.x += x; mView.mPosition.z += z; mView.mTarget.x += x; mView.mTarget.z += z; // TODO maybe inline getGameCam1()->setView(mView); getGameCam1()->onFlag(0x1); getGameCam1()->onFlag(0x4000); updateView(); apply(); } int dCamera_c::execute() { dAcPy_c *link = dAcPy_c::GetLinkM(); checkCameraChange(); for (int i = 0; i < CAM_MAX; i++) { if (mpCameras[i] != nullptr) { mpCameras[i]->doExecute(); } } if (mActiveCameraIdx == CAM_EVENT) { // hmmmm static_cast(mpCameras[0])->clearCamIds(); } updateView(); apply(); field_0xDC0.fn_8019E940(); field_0xDCC = false; if (mActiveCameraIdx == CAM_EVENT) { onFlag(CAM_FLAGS_IN_EVENT); } if (checkFlag(CAM_FLAGS_0x100 | CAM_FLAGS_IN_EVENT)) { field_0x290 += (1.0f / sHio.field_0x24); if (field_0x290 > 1.0f) { field_0x290 = 1.0f; } } else { field_0x290 = field_0x290 - (1.0f / sHio.field_0x24); if (field_0x290 < 0.0f) { field_0x290 = 0.0f; } } f32 scale = checkFlag(CAM_FLAGS_IN_EVENT) != 0 ? getEventLetterboxAmount() : sHio.field_0x20; f32 target = camEaseInOut(field_0x290, 1.0f); mLetterboxAmount += (target * scale - mLetterboxAmount) * 0.5f; if (mActiveCameraIdx == CAM_EVENT && checkFlag(CAM_FLAGS_NO_LETTERBOX_IN_EVENT) != 0) { mLetterboxAmount = 0.0f; } dScGame_c::GetInstance()->setTargetingScreenLetterboxAmount(mLetterboxAmount); offFlag(CAM_FLAGS_0x100 | CAM_FLAGS_IN_EVENT); if (!link->checkActionFlagsCont(0x400000)) { field_0x299 = 0; } f32 alphaTarget; f32 alphaUpdateSpeed = sHio.field_0x2C; if (EventManager::isInEvent()) { alphaUpdateSpeed = 1.0f; if (field_0x2A1 != 0) { alphaTarget = field_0x2A0 ? sHio.field_0x28 : 0.0f; } else { alphaTarget = 1.0f; } } else { if (field_0x299 != 0) { alphaTarget = field_0x298 ? sHio.field_0x28 : 0.0f; } else { alphaTarget = 1.0f; } } mGlobalAlpha += alphaUpdateSpeed * (alphaTarget - mGlobalAlpha); dStageMgr_c::GetInstance()->setGlobalAlpha((u8)(mGlobalAlpha * 255.1f) & 0xFF); return SUCCEEDED; } void dCamera_c::checkCameraChange() { offFlag(CAM_FLAGS_0x10); if (checkFlag(CAM_FLAGS_0x4)) { getGameCam1()->onFlag(0x400); offFlag(CAM_FLAGS_0x4); } if (checkFlag(CAM_FLAGS_MAP) && mActiveCameraIdx == CAM_MAP && !mMapCam.isActiveOrAnimating()) { offFlag(CAM_FLAGS_MAP); onFlag(CAM_FLAGS_0x4); getGameCam1()->onFlag(0x10000); debugPrintf7("off map"); } if (EventManager::isInEvent()) { onFlag(CAM_FLAGS_EVENT); offFlag(CAM_FLAGS_MAP); } else { offFlag(CAM_FLAGS_EVENT); } if (checkFlag(CAM_FLAGS_EVENT)) { if (mActiveCameraIdx != CAM_EVENT) { setActiveCamera(CAM_EVENT); } } else if (checkFlag(CAM_FLAGS_MAP)) { if (mActiveCameraIdx != CAM_MAP) { setActiveCamera(CAM_MAP); } } else { if (mActiveCameraIdx != CAM_GAME_0) { setActiveCamera(CAM_GAME_0); } } } void dCamera_c::updateView() { if (field_0xDCC) { mView = mView1; field_0xDCC = 0; } else { mView = mpCameras[mActiveCameraIdx]->getView(); } mVec3_c dir = mView.mTarget - mView.mPosition; mYAngle = cM::atan2s(dir.y, dir.absXZ()); mXZAngle = cM::atan2s(dir.x, dir.z); } int dCamera_c::draw() { if (mpCameras[mActiveCameraIdx] != nullptr) { mpCameras[mActiveCameraIdx]->doDraw(); } return SUCCEEDED; } bool dCamera_c::isUnderwater() const { return checkFlag(CAM_FLAGS_UNDERWATER); } void dCamera_c::apply() { updateUnderwaterDepth(mView.mPosition); if (isUnderwater_()) { onFlag(CAM_FLAGS_UNDERWATER); } else { offFlag(CAM_FLAGS_UNDERWATER); } if (mScreenShaker.execute()) { mView.mPosition += mScreenShaker.getShakeOffset(); mView.mTarget += mScreenShaker.getShakeOffset(); } mLookAtCamera.mPos = mView.mPosition; mLookAtCamera.mAt = mView.mTarget; if (mLookAtCamera.mAt.x == mLookAtCamera.mPos.x && mLookAtCamera.mAt.z == mLookAtCamera.mPos.z) { mLookAtCamera.mPos.z += 1.0f; } mLookAtCamera.mUp = mVec3_c::Ey; mLookAtCamera.doUpdateMatrix(); mMtx.copyFrom(mLookAtCamera.getViewMatrix()); // TODO: Maybe mutating mtx wasn't intended here but it's probably unproblematic mMtx.inverse(); mMtxInv = mMtx; mMtxInv.m[0][3] = 0.0f; mMtxInv.m[1][3] = 0.0f; mMtxInv.m[2][3] = 0.0f; dSnd3DManager_c::GetInstance()->updateFromCamera(mLookAtCamera); applyTilt(); setFrustum(mView.mFov, sHio.field_0x04, sHio.field_0x08); nw4r::g3d::Camera cam = dStageMgr_c::GetInstance()->getCamera(mMyCameraIndex); mpScreen->CopyToG3D(cam); mLookAtCamera.setG3DCamera(cam); } s32 dCamera_c::setActiveCamera(s32 newCamIdx) { if (mpCameras[newCamIdx] == nullptr) { return mActiveCameraIdx; } else if (mActiveCameraIdx == newCamIdx) { return newCamIdx; } else { for (int i = 0; i < CAM_MAX; i++) { mpCameras[i]->deactivate(); } mpCameras[newCamIdx]->activate(); debugPrintf7("unit %d -> %d", mActiveCameraIdx, newCamIdx); mActiveCameraIdx = newCamIdx; return newCamIdx; } } f32 dCamera_c::getEventLetterboxAmount() { return sHio.field_0x1C; } void dCamera_c::setFrustum(f32 fov, f32 near, f32 far) { if (dGfx_c::isTvMode4To3()) { mpScreen->Reset( 0.0f, dGfx_c::getEFBHeightDifferenceF(), dGfx_c::getCurrentScreenWidthF(), dGfx_c::getHeightScaledF(), mpScreen->GetCanvasMode() ); } else { mpScreen->Reset( 0.0f, 0.0f, dGfx_c::getCurrentScreenWidthF(), dGfx_c::getCurrentScreenHeightF(), mpScreen->GetCanvasMode() ); if (dGfx_c::isTvMode4To3()) { // Unreachable... fov *= sHio.field_0x0C; } } mpScreen->SetProjectionType(EGG::Frustum::PROJ_PERSP); mpScreen->SetNearZ(near); mpScreen->SetFarZ(far); mpScreen->SetFovy(fov); // Possible fakematch: Either the internals of EGG::Matrix34f allow // copying the data via compiler-generated struct copies (lwz/stw), // or this is really what they did. NSMBW symbols confirm that this function // takes mMtx_c while it's clear that EGG cameras return EGG matrices. mMtx_c mtx = *(mMtx_c *)&mLookAtCamera.getViewMatrix(); mFrustum.set(fov, mpScreen->GetAspect(), near, far, mtx); } mAng dCamera_c::getYAngle() const { return mYAngle; } mAng dCamera_c::getYRot() const { return field_0xDC0.fn_8019E930(); } mAng dCamera_c::getXZAngle() const { return mXZAngle; } bool dCamera_c::fn_8019E3C0() const { if (mActiveCameraIdx == CAM_GAME_0) { return mGameCam1.getField_0x078() == true; } else if (mActiveCameraIdx == CAM_GAME_1) { return mGameCam2.getField_0x078() == true; } return false; } void dCamera_c::enterMap() { if (!checkFlag(CAM_FLAGS_MAP)) { onFlag(CAM_FLAGS_MAP); } } void dCamera_c::leaveMap() { if (checkFlag(CAM_FLAGS_MAP)) { mMapCam.startOut(); } } void dCamera_c::applyTilt() { EGG::Matrix34f &mtx = mLookAtCamera.getViewMatrix(); mMtx_c rotMtx; rotMtx.ZrotS(mAng::fromDeg(-mView.mTilt)); MTXConcat(rotMtx, mtx.m, mtx.m); } bool dCamera_c::fn_8019E4D0() const { dAcPy_c *link = dAcPy_c::GetLinkM(); return !EventManager::isInEvent() && !(link != nullptr && link->checkActionFlagsCont(0x5d7)); } void dCamera_c::updateUnderwaterDepth(const mVec3_c &pos) { mVec3_c chkPos = pos; dBgS_RoofChk roofChk; roofChk.SetPos(&chkPos); roofChk.SetUnderwaterRoof(); roofChk.SetField_0x7C(1); f32 f = dBgS::GetInstance()->RoofChk(&roofChk); if (f != 1e9f) { chkPos.y = f; } else { chkPos.y += 10000.0f; } dBgS_ObjGndChk_Wtr wtrChk; wtrChk.SetPos(chkPos); f32 waterHeight = dBgS::GetInstance()->GroundCross(&wtrChk); if (waterHeight != 1e9f) { mIsUnderwater = waterHeight > pos.y; mWaterHeight = waterHeight; } else { mIsUnderwater = false; mWaterHeight = -1e9f; } if (mIsUnderwater) { mUnderwaterDepth = waterHeight - pos.y; } else { mUnderwaterDepth = 0.0f; } } bool dCamera_c::isUnderwater_() const { return mIsUnderwater; } f32 dCamera_c::getUnderwaterDepth() const { return mUnderwaterDepth; } bool dCamera_c::screen_shaker::execute() { mShakeOffset = mVec3_c::Zero; bool ret = false; if (mScreenShakeIntensity > 0.001f) { static s32 upOrDown = 0; upOrDown = upOrDown ^ 1; f32 f; if (upOrDown) { f = 400.0f; } else { f = -400.0f; } mVec3_c shakeOffset(cM::rndFX(100.0f), cM::rndF(f), 0.0f); shakeOffset.normalize(); // Note: the difference in coordinates here is due to the camera view using a different // coordinate convention than game code. shakeOffset.rotX(mpCamera->getYAngle()); shakeOffset.rotY(mpCamera->getXZAngle()); mVec3_c chckVector = shakeOffset * (mScreenShakeIntensity + 5.0f); shakeOffset *= mScreenShakeIntensity; mVec3_c pos = mpCamera->getPosition(); mVec3_c dest = pos + chckVector; dBgS_CamLinChk chk; chk.Set(&pos, &dest, nullptr); if (!dBgS::GetInstance()->LineCross(&chk)) { mShakeOffset = shakeOffset; ret = true; } } mScreenShakeIntensity = 0.0f; return ret; } mVec3_c dCamera_c::screen_shaker::getShakeOffset() const { return mShakeOffset; } void dCamera_c::substruct_1::setCamera(dCamera_c *cam) { mpCamera = cam; } void dCamera_c::substruct_1::fn_8019E890() { fn_8019E8D0(mpCamera->getXZAngle()); } void dCamera_c::substruct_1::fn_8019E8D0(const mAng &ang) { if (!mActive) { mActive = true; field_0x04 = ang; mFSStickAngle = dPad::getFSStickAngle(); } } mAng dCamera_c::substruct_1::fn_8019E930() const { return field_0x04; } void dCamera_c::substruct_1::fn_8019E940() { if (mActive) { mAng angle = mFSStickAngle - dPad::getFSStickAngle(); f32 distance = dPad::getFSStickDistance(); if (distance < sHio.field_0x14 || mAng::abs(angle) > sHio.field_0x18) { mActive = false; } } if (!mActive) { field_0x04 = mpCamera->getXZAngle(); mFSStickAngle = dPad::getFSStickAngle(); } } bool dCamera_c::setEventCamView(const mVec3_c &target, const mVec3_c &pos, f32 fov, f32 tilt) { CamView view(pos, target, fov, tilt); getEventCam()->setView(view); return true; } bool dCamera_c::fn_8019EA70(bool b) { CamView view = getEventCam()->getView(); mVec3_c dest; dest = camGetPointOnLine(view.mTarget, view.mPosition, dAcPy_c::GetLinkM()->mPositionCopy3); view.mTarget = dest; // TODO maybe inline getGameCam1()->setView(view); getGameCam1()->onFlag(0x1); if (b) { getGameCam1()->onFlag(0x4000); } else { getGameCam1()->offFlag(0x200); } return true; } f32 dCamera_c::fn_8019EB90() { return getGameCam1()->getField_0x0AC(); }