Skip to content

Commit 94a5c70

Browse files
committed
Fix button prompts not appearing for shrines, terminals etc.
So, the dungeon exits (but also many other button prompts, like the dungeon entrances, and interactables) use three AttClient checks to see whether the player should be shown the option to interact with it: - Screen check (uses the camera to check whether the interactor (or AttClient) is visible) - AreaCylinderFan check (boundary box) - Line (of sight) check (using a raycast) However, the Screen check was basically broken due to the VR rendering using a different aspect ratio but also due to having two frustums instead of one. I added a custom visibility query check (which the screen check uses underneath), but it broke enemies (presumably), so for now I just hardcoded a workaround that just patches the Screen checks to always return visible.
1 parent 1f1e404 commit 94a5c70

File tree

5 files changed

+178
-17
lines changed

5 files changed

+178
-17
lines changed

include/game_structs.h

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,57 @@ struct ActCamera : ActorWiiU {
524524
static_assert(offsetof(ActCamera, origCamMtx) == 0x550, "ActCamera.origCamMtx offset mismatch");
525525
static_assert(offsetof(ActCamera, finalCamMtx) == 0x5C0, "ActCamera.finalCamMtx offset mismatch");
526526

527+
struct BESeadCamera {
528+
BEMatrix34 mtx;
529+
BEType<uint32_t> __vftable;
530+
};
531+
struct BESeadLookAtCamera : BESeadCamera {
532+
BEVec3 pos;
533+
BEVec3 at;
534+
BEVec3 up;
535+
536+
bool operator==(const BESeadLookAtCamera& other) const {
537+
return pos == other.pos && at == other.at && up == other.up;
538+
}
539+
};
540+
static_assert(sizeof(BESeadCamera) == 0x34, "BESeadCamera size mismatch");
541+
static_assert(sizeof(BESeadLookAtCamera) == 0x58, "BESeadLookAtCamera size mismatch");
542+
543+
// not identical memory layout wise
544+
struct Frustum {
545+
glm::vec4 planes[6];
546+
547+
void update(const glm::mat4& vp) {
548+
// Left
549+
planes[0] = glm::vec4(vp[0][3] + vp[0][0], vp[1][3] + vp[1][0], vp[2][3] + vp[2][0], vp[3][3] + vp[3][0]);
550+
// Right
551+
planes[1] = glm::vec4(vp[0][3] - vp[0][0], vp[1][3] - vp[1][0], vp[2][3] - vp[2][0], vp[3][3] - vp[3][0]);
552+
// Bottom
553+
planes[2] = glm::vec4(vp[0][3] + vp[0][1], vp[1][3] + vp[1][1], vp[2][3] + vp[2][1], vp[3][3] + vp[3][1]);
554+
// Top
555+
planes[3] = glm::vec4(vp[0][3] - vp[0][1], vp[1][3] - vp[1][1], vp[2][3] - vp[2][1], vp[3][3] - vp[3][1]);
556+
// Near
557+
planes[4] = glm::vec4(vp[0][3] + vp[0][2], vp[1][3] + vp[1][2], vp[2][3] + vp[2][2], vp[3][3] + vp[3][2]);
558+
// Far
559+
planes[5] = glm::vec4(vp[0][3] - vp[0][2], vp[1][3] - vp[1][2], vp[2][3] - vp[2][2], vp[3][3] - vp[3][2]);
560+
561+
for (int i = 0; i < 6; ++i) {
562+
float len = glm::length(glm::vec3(planes[i]));
563+
planes[i] /= len;
564+
}
565+
}
566+
567+
bool checkSphere(const glm::vec3& center, float radius) const {
568+
for (int i = 0; i < 6; ++i) {
569+
float dist = glm::dot(glm::vec3(planes[i]), center) + planes[i].w;
570+
if (dist < -radius) {
571+
return false;
572+
}
573+
}
574+
return true;
575+
}
576+
};
577+
527578
#pragma pack(pop)
528579

529580
inline std::string contactLayerNames[] = {

include/pch.h

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -550,23 +550,6 @@ struct BESeadPerspectiveProjection : BESeadProjection {
550550
static_assert(sizeof(BESeadProjection) == 0x94, "BESeadProjection size mismatch");
551551
static_assert(sizeof(BESeadPerspectiveProjection) == 0xB8, "BESeadPerspectiveProjection size mismatch");
552552

553-
#pragma pack(push, 1)
554-
struct BESeadCamera {
555-
BEMatrix34 mtx;
556-
BEType<uint32_t> __vftable;
557-
};
558-
struct BESeadLookAtCamera : BESeadCamera {
559-
BEVec3 pos;
560-
BEVec3 at;
561-
BEVec3 up;
562-
563-
bool operator==(const BESeadLookAtCamera& other) const {
564-
return pos == other.pos && at == other.at && up == other.up;
565-
}
566-
};
567-
#pragma pack(pop)
568-
static_assert(sizeof(BESeadCamera) == 0x34, "BESeadCamera size mismatch");
569-
static_assert(sizeof(BESeadLookAtCamera) == 0x58, "BESeadLookAtCamera size mismatch");
570553
struct data_VRProjectionMatrixOut {
571554
BEType<float> aspectRatio;
572555
BEType<float> fovY;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[BetterVR_FirstPersonMode_V208]
2+
moduleMatches = 0x6267BFD0
3+
4+
.origin = codecave
5+
6+
custom_checkIfCameraCanSeePos:
7+
mflr r0
8+
stw r0, 4(r1)
9+
bla import.coreinit.hook_CheckIfCameraCanSeePos
10+
lwz r0, 4(r1)
11+
mtlr r0
12+
blr
13+
14+
;0x0318FFA8 = ba custom_checkIfCameraCanSeePos
15+
16+
; always make AttCheck::ScreenRelated check return true
17+
0x035D67BC = li r3, 1

src/hooking/camera.cpp

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,112 @@ void CemuHooks::hook_ModifyProjectionUsingCamera(PPCInterpreter_t* hCPU) {
538538
writeMemory(projectionPtr, &perspectiveProjection);
539539
}
540540

541+
std::pair<glm::vec3, glm::fquat> CemuHooks::CalculateVRWorldPose(const BESeadLookAtCamera& camera, uint8_t side) {
542+
// in-game camera
543+
glm::mat4x3 viewMatrix = camera.mtx.getLEMatrix();
544+
glm::mat4 worldGame = glm::inverse(glm::mat4(viewMatrix));
545+
glm::vec3 basePos = s_wsCameraPosition;
546+
glm::quat baseRot = s_wsCameraRotation;
547+
auto [swing, baseYaw] = swingTwistY(baseRot);
548+
549+
if (CemuHooks::IsFirstPerson()) {
550+
// take link's direction, then rotate the headset position
551+
BEMatrix34 playerMtx = {};
552+
readMemory(s_playerMtxAddress, &playerMtx);
553+
glm::fvec3 playerPos = playerMtx.getPos().getLE();
554+
555+
if (s_isRiding) {
556+
playerPos.y -= hardcodedRidingOffset;
557+
}
558+
else if (s_isSwimming) {
559+
playerPos.y += hardcodedSwimOffset;
560+
}
561+
else {
562+
playerPos.y += GetSettings().playerHeightSetting.getLE();
563+
}
564+
565+
basePos = playerPos;
566+
567+
if (auto settings = GetFirstPersonSettingsForActiveEvent()) {
568+
if (settings->ignoreCameraRotation) {
569+
glm::fquat playerRot = playerMtx.getRotLE();
570+
auto [swing, yaw] = swingTwistY(playerRot);
571+
baseYaw = yaw * glm::angleAxis(glm::radians(180.0f), glm::fvec3(0.0f, 1.0f, 0.0f));
572+
}
573+
}
574+
}
575+
576+
// vr camera
577+
std::optional<XrPosef> currPoseOpt = VRManager::instance().XR->GetRenderer()->GetPose((OpenXR::EyeSide)side);
578+
if (!currPoseOpt.has_value()) {
579+
return { basePos, baseRot };
580+
}
581+
582+
glm::fvec3 eyePos = ToGLM(currPoseOpt.value().position);
583+
glm::fquat eyeRot = ToGLM(currPoseOpt.value().orientation);
584+
585+
glm::vec3 newPos = basePos + (baseYaw * eyePos);
586+
glm::fquat newRot = baseYaw * eyeRot;
587+
588+
return { newPos, newRot };
589+
}
590+
591+
constexpr float VISIBILITY_CHECK_BUFFER = 1.5f;
592+
void CemuHooks::hook_CheckIfCameraCanSeePos(PPCInterpreter_t* hCPU) {
593+
hCPU->instructionPointer = hCPU->sprNew.LR;
594+
595+
if (VRManager::instance().XR->GetRenderer() == nullptr) {
596+
hCPU->gpr[3] = 1;
597+
return;
598+
}
599+
600+
uint32_t camPtr = hCPU->gpr[3];
601+
uint32_t posPtr = hCPU->gpr[4];
602+
float radius = hCPU->fpr[1].fp0;
603+
float nearClip = hCPU->fpr[2].fp0;
604+
float farClip = hCPU->fpr[3].fp0;
605+
606+
BESeadLookAtCamera camera = {};
607+
readMemory(camPtr, &camera);
608+
609+
struct {
610+
uint32_t x, y, z;
611+
} posRaw;
612+
readMemory(posPtr, &posRaw);
613+
614+
auto swapFloat = [](uint32_t val) -> float {
615+
uint32_t swapped = ((val & 0xFF) << 24) | ((val & 0xFF00) << 8) | ((val & 0xFF0000) >> 8) | ((val & 0xFF000000) >> 24);
616+
float f;
617+
memcpy(&f, &swapped, 4);
618+
return f;
619+
};
620+
621+
glm::vec3 center(swapFloat(posRaw.x), swapFloat(posRaw.y), swapFloat(posRaw.z));
622+
623+
Frustum frustum;
624+
bool visible = false;
625+
626+
for (int i = 0; i < 2; ++i) {
627+
OpenXR::EyeSide side = (i == 0) ? OpenXR::EyeSide::LEFT : OpenXR::EyeSide::RIGHT;
628+
if (auto fovOpt = VRManager::instance().XR->GetRenderer()->GetFOV(side)) {
629+
auto [pos, rot] = CalculateVRWorldPose(camera, side);
630+
glm::mat4 view = glm::inverse(glm::translate(glm::mat4(1.0f), pos) * glm::mat4_cast(rot));
631+
glm::mat4 proj = calculateProjectionMatrix(nearClip, farClip, fovOpt.value());
632+
glm::mat4 vp = proj * view;
633+
634+
frustum.update(vp);
635+
if (frustum.checkSphere(center, radius+VISIBILITY_CHECK_BUFFER)) {
636+
visible = true;
637+
break;
638+
}
639+
}
640+
}
641+
642+
Log::print<INFO>("Checking visibility of {} (rad = {}, near = {}, far = {}): {}", center, radius, nearClip, farClip, visible ? "visible" : "invisible");
643+
644+
hCPU->gpr[3] = visible ? 1 : 0;
645+
}
646+
541647
void CemuHooks::hook_EndCameraSide(PPCInterpreter_t* hCPU) {
542648
hCPU->instructionPointer = hCPU->sprNew.LR;
543649

src/hooking/cemu_hooks.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class CemuHooks {
3232
osLib_registerHLEFunction("coreinit", "hook_ModifyLightPrePassProjectionMatrix", &hook_ModifyLightPrePassProjectionMatrix);
3333
osLib_registerHLEFunction("coreinit", "hook_OverwriteSeadPerspectiveProjectionSet", &hook_OverwriteSeadPerspectiveProjectionSet);
3434
osLib_registerHLEFunction("coreinit", "hook_ModifyProjectionUsingCamera", &hook_ModifyProjectionUsingCamera);
35+
osLib_registerHLEFunction("coreinit", "hook_CheckIfCameraCanSeePos", &hook_CheckIfCameraCanSeePos);
3536
osLib_registerHLEFunction("coreinit", "hook_UpdateCameraForGameplay", &hook_UpdateCameraForGameplay);
3637
osLib_registerHLEFunction("coreinit", "hook_GetRenderCamera", &hook_GetRenderCamera);
3738
osLib_registerHLEFunction("coreinit", "hook_GetRenderProjection", &hook_GetRenderProjection);
@@ -197,6 +198,8 @@ class CemuHooks {
197198

198199
static bool IsScreenOpen(ScreenId screen);
199200
static void InitWindowHandles();
201+
static std::pair<glm::vec3, glm::fquat> CalculateVRWorldPose(const BESeadLookAtCamera& camera, uint8_t side);
202+
200203
static void hook_UpdateSettings(PPCInterpreter_t* hCPU);
201204

202205
// Actor Hooks
@@ -207,6 +210,7 @@ class CemuHooks {
207210
static void hook_BeginCameraSide(PPCInterpreter_t* hCPU);
208211
static void hook_ModifyLightPrePassProjectionMatrix(PPCInterpreter_t* hCPU);
209212
static void hook_ModifyProjectionUsingCamera(PPCInterpreter_t* hCPU);
213+
static void hook_CheckIfCameraCanSeePos(PPCInterpreter_t* hCPU);
210214
static void hook_OverwriteSeadPerspectiveProjectionSet(PPCInterpreter_t* hCPU);
211215
static void hook_UpdateCameraForGameplay(PPCInterpreter_t* hCPU);
212216
static void hook_GetRenderCamera(PPCInterpreter_t* hCPU);

0 commit comments

Comments
 (0)