Skip to content

Commit 50451eb

Browse files
committed
Add world-space debug visualizer
1 parent d567fd4 commit 50451eb

File tree

10 files changed

+453
-24
lines changed

10 files changed

+453
-24
lines changed

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ target_sources(BetterVR_Layer PUBLIC
6969
${CMAKE_CURRENT_SOURCE_DIR}/src/utils/update_checker.cpp
7070
${CMAKE_CURRENT_SOURCE_DIR}/src/utils/update_checker.h
7171
${CMAKE_CURRENT_SOURCE_DIR}/src/utils/mod_settings.h
72+
${CMAKE_CURRENT_SOURCE_DIR}/src/utils/debug_draw.h
73+
${CMAKE_CURRENT_SOURCE_DIR}/src/utils/debug_draw.cpp
7274
${CMAKE_CURRENT_SOURCE_DIR}/src/hooking/framebuffer.cpp
7375
${CMAKE_CURRENT_SOURCE_DIR}/src/hooking/framebuffer.h
7476
${CMAKE_CURRENT_SOURCE_DIR}/src/hooking/layer.cpp

include/game_structs.h

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,54 @@ struct Frustum {
588588
}
589589
};
590590

591+
namespace ksys::phys {
592+
enum GroundHit {
593+
Player = 0x0,
594+
Animal = 0x1,
595+
NPC = 0x2,
596+
Camera = 0x3,
597+
AttackHitPlayer = 0x4,
598+
AttackHitEnemy = 0x5,
599+
Arrow = 0x6,
600+
Bomb = 0x7,
601+
Magnet = 0x8,
602+
CameraBody = 0x9,
603+
IK = 0xA,
604+
Grudge = 0xB,
605+
MovingTrolley = 0xC,
606+
LineOfSight = 0xD,
607+
Giant = 0xE,
608+
HitAll = 0xF,
609+
Ignore = 0x10,
610+
};
611+
612+
struct RayCast {
613+
BEVec3 from;
614+
BEVec3 to;
615+
BEType<uint32_t> groupHandlerPtr;
616+
GroundHit groundHit;
617+
uint8_t gap20[40];
618+
char char48;
619+
uint8_t gap49[7];
620+
BEType<uint32_t> dword50;
621+
BEType<uint32_t> dword54;
622+
sead::PtrArrayImpl mLayerMasks;
623+
BEType<uint32_t> dword64;
624+
uint8_t byte68;
625+
uint8_t byte69;
626+
uint8_t byte6A;
627+
sead::PtrArrayImpl mIgnoredGroups;
628+
uint8_t gap78[16];
629+
BEType<uint32_t> dword88;
630+
BEType<uint32_t> dword8C;
631+
BEType<uint32_t> __vftable;
632+
};
633+
634+
struct RayCastBodyQuery : RayCast {
635+
BEType<uint32_t> mHitRigidBody;
636+
};
637+
}
638+
591639
#pragma pack(pop)
592640

593641
inline std::string contactLayerNames[] = {

resources/BreathOfTheWild_BetterVR/patch_CTRL_Logging.asm

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,9 @@ mr r29, r4
6161
blr
6262

6363
0x0398865C = bla logModelAccessSearch
64+
65+
; ======================================================================
66+
67+
; get hit position
68+
69+
0x034CA030 = ba import.coreinit.hook_VisualizeRayCastHits

src/hooking/camera.cpp

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#include "utils/debug_draw.h"
12
#include "cemu_hooks.h"
23
#include "instance.h"
34
#include "rendering/openxr.h"
@@ -193,28 +194,6 @@ void CemuHooks::hook_UpdateCameraForGameplay(PPCInterpreter_t* hCPU) {
193194
s_framesSinceLastCameraUpdate = 0;
194195
}
195196

196-
// turns out this only does the minimap ui, and not the stamina UI :/
197-
//void CemuHooks::hook_UpdateUIPosition(PPCInterpreter_t* hCPU) {
198-
// hCPU->instructionPointer = hCPU->sprNew.LR;
199-
//
200-
// EyeSide side = hCPU->gpr[10] == 0 ? EyeSide::LEFT : EyeSide::RIGHT;
201-
// uint32_t currFrameCounter = hCPU->gpr[11];
202-
// uint32_t doesUIManagerExist = hCPU->gpr[3] != 0;
203-
// uint32_t uiManagerInstance = hCPU->gpr[12];
204-
//
205-
// if (!doesUIManagerExist) {
206-
// return;
207-
// }
208-
//
209-
// BEVec3 playerPosCopy = getMemory<BEVec3>(uiManagerInstance + offsetof(UIManager, innerArray.uiPos1));
210-
// BEVec3 playerMtxPositionCopy = getMemory<BEVec3>(uiManagerInstance + offsetof(UIManager, innerArray.uiPos2));
211-
//
212-
// Log::print<INFO>("[{}] Updating UI position (frame = {}, playerPos = {}, playerMtxPos = {})", side, currFrameCounter, playerPosCopy, playerMtxPositionCopy);
213-
//
214-
// writeMemory(uiManagerInstance + offsetof(UIManager, innerArray.uiPos1), &playerPosCopy);
215-
// writeMemory(uiManagerInstance + offsetof(UIManager, innerArray.uiPos2), &playerMtxPositionCopy);
216-
//}
217-
218197
void CemuHooks::hook_FixStaminaGaugeScreenPosition(PPCInterpreter_t* hCPU) {
219198
hCPU->instructionPointer = hCPU->sprNew.LR;
220199

@@ -397,7 +376,7 @@ static glm::mat4 calculateProjectionMatrix(float nearZ, float farZ, const XrFovf
397376
void CemuHooks::hook_GetRenderProjection(PPCInterpreter_t* hCPU) {
398377
hCPU->instructionPointer = hCPU->sprNew.LR;
399378

400-
if (CemuHooks::UseBlackBarsDuringEvents()) {
379+
if (UseBlackBarsDuringEvents()) {
401380
return;
402381
}
403382

@@ -1092,4 +1071,50 @@ void CemuHooks::hook_PlayerLadderFix(PPCInterpreter_t* hCPU) {
10921071
if (IsFirstPerson()) {
10931072
s_isLadderClimbing = 2;
10941073
}
1095-
}
1074+
}
1075+
1076+
void CemuHooks::hook_VisualizeRayCastHits(PPCInterpreter_t* hCPU) {
1077+
hCPU->instructionPointer = hCPU->sprNew.LR;
1078+
1079+
if (VRManager::instance().XR->GetRenderer() == nullptr) {
1080+
return;
1081+
}
1082+
1083+
uint32_t rayCastResultPtr = hCPU->gpr[3];
1084+
glm::fvec3 raycastHitPos = getMemory<BEVec3>(hCPU->gpr[4]).getLE();
1085+
1086+
ksys::phys::RayCast rayCast = {};
1087+
readMemory(rayCastResultPtr, &rayCast);
1088+
1089+
glm::fvec3 rayStart = rayCast.from.getLE();
1090+
glm::fvec3 rayEnd = rayCast.to.getLE();
1091+
1092+
rayStart.y += 0.5f;
1093+
rayEnd.y += 0.5f;
1094+
raycastHitPos.y += 0.5f;
1095+
1096+
DebugDraw::instance().Line(rayStart, raycastHitPos, IM_COL32(255, 0, 255, 255));
1097+
DebugDraw::instance().Line(raycastHitPos, rayEnd, IM_COL32(128, 0, 128, 128));
1098+
}
1099+
1100+
// turns out this only does the minimap ui, and not the stamina UI :/
1101+
//void CemuHooks::hook_UpdateUIPosition(PPCInterpreter_t* hCPU) {
1102+
// hCPU->instructionPointer = hCPU->sprNew.LR;
1103+
//
1104+
// EyeSide side = hCPU->gpr[10] == 0 ? EyeSide::LEFT : EyeSide::RIGHT;
1105+
// uint32_t currFrameCounter = hCPU->gpr[11];
1106+
// uint32_t doesUIManagerExist = hCPU->gpr[3] != 0;
1107+
// uint32_t uiManagerInstance = hCPU->gpr[12];
1108+
//
1109+
// if (!doesUIManagerExist) {
1110+
// return;
1111+
// }
1112+
//
1113+
// BEVec3 playerPosCopy = getMemory<BEVec3>(uiManagerInstance + offsetof(UIManager, innerArray.uiPos1));
1114+
// BEVec3 playerMtxPositionCopy = getMemory<BEVec3>(uiManagerInstance + offsetof(UIManager, innerArray.uiPos2));
1115+
//
1116+
// Log::print<INFO>("[{}] Updating UI position (frame = {}, playerPos = {}, playerMtxPos = {})", side, currFrameCounter, playerPosCopy, playerMtxPositionCopy);
1117+
//
1118+
// writeMemory(uiManagerInstance + offsetof(UIManager, innerArray.uiPos1), &playerPosCopy);
1119+
// writeMemory(uiManagerInstance + offsetof(UIManager, innerArray.uiPos2), &playerMtxPositionCopy);
1120+
//}

src/hooking/cemu_hooks.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ class CemuHooks {
7676
osLib_registerHLEFunction("coreinit", "hook_CreateNewScreen", &hook_CreateNewScreen);
7777
osLib_registerHLEFunction("coreinit", "hook_FixUIBlending", &hook_FixUIBlending);
7878
osLib_registerHLEFunction("coreinit", "hook_FixCameraSaveFilesAndInventory", &hook_FixCameraSaveFilesAndInventory);
79+
osLib_registerHLEFunction("coreinit", "hook_VisualizeRayCastHits", &hook_VisualizeRayCastHits);
7980
};
8081
~CemuHooks() {
8182
FreeLibrary(m_cemuHandle);
@@ -240,6 +241,7 @@ class CemuHooks {
240241
static void hook_GetEventName(PPCInterpreter_t* hCPU);
241242
static void hook_OverwriteCameraParam(PPCInterpreter_t* hCPU);
242243
static void hook_PlayerLadderFix(PPCInterpreter_t* hCPU);
244+
static void hook_VisualizeRayCastHits(PPCInterpreter_t* hCPU);
243245
static void hook_FixLadder(PPCInterpreter_t* hCPU);
244246
static void hook_PlayerIsRiding(PPCInterpreter_t* hCPU);
245247
static void hook_PlayerIsRidingSandSeal(PPCInterpreter_t* hCPU);

src/hooking/entity_debugger.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <imgui_memory_editor.h>
77

88
#include "implot3d_internal.h"
9+
#include "utils/debug_draw.h"
910

1011
std::mutex g_actorListMutex;
1112
std::unordered_map<uint32_t, std::pair<std::string, uint32_t>> s_knownActors;
@@ -193,6 +194,21 @@ void EntityDebugger::UpdateEntityMemory() {
193194
SetAABB(actorId, aabbMin.getLE(), aabbMax.getLE());
194195
}
195196

197+
if (float distance = glm::distance(m_playerPos, mtx.getPos().getLE()); distance <= 100.0f) {
198+
glm::fvec3 pos = mtx.getPos().getLE();
199+
glm::fquat rot = mtx.getRotLE();
200+
201+
glm::fvec3 localMin = aabbMin.getLE();
202+
glm::fvec3 localMax = aabbMax.getLE();
203+
glm::fvec3 localCenter = (localMin + localMax) * 0.5f;
204+
glm::fvec3 halfExtents = (localMax - localMin) * 0.5f;
205+
206+
// Transform local AABB center to world space
207+
glm::fvec3 worldCenter = pos + glm::mat3_cast(rot) * localCenter;
208+
209+
DebugDraw::instance().Box(worldCenter, halfExtents, rot, IM_COL32(255, 255, 255, 255/10), 1.0f);
210+
}
211+
196212
// uint32_t physicsMtxPtr = 0;
197213
// if (readMemoryBE(actorPtr + offsetof(ActorWiiU, physicsMtxPtr), &physicsMtxPtr); physicsMtxPtr != 0) {
198214
// overlay->AddOrUpdateEntity(actorId, actorName, "physicsMtx", physicsMtxPtr, getMemory<BEMatrix34>(physicsMtxPtr));

src/hooking/framebuffer.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include "instance.h"
33
#include "layer.h"
44
#include "utils/vulkan_utils.h"
5+
#include "utils/debug_draw.h"
56

67

78
std::mutex lockImageResolutions;
@@ -256,6 +257,10 @@ void VkDeviceOverrides::CmdClearColorImage(const vkroots::VkCommandBufferDispatc
256257
imguiOverlay->Render(frameIdx, true);
257258
imguiOverlay->Update();
258259
imguiOverlay->DrawAndCopyToImage(commandBuffer, image, frameIdx);
260+
261+
// Clear debug draw primitives now that both eyes have been rendered
262+
DebugDraw::instance().Clear();
263+
259264
returnToLayout();
260265
return;
261266
}

src/rendering/vulkan_imgui.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include "hooking/entity_debugger.h"
33
#include "instance.h"
44
#include "utils/vulkan_utils.h"
5+
#include "utils/debug_draw.h"
56
#include "vulkan.h"
67
#include "utils/mod_settings.h"
78

@@ -409,6 +410,16 @@ void RND_Renderer::ImGuiOverlay::Render(long frameIdx, bool renderBackground) {
409410

410411
ImGui::PopStyleVar();
411412
ImGui::PopStyleVar();
413+
414+
// Render world-space debug primitives clipped to the 3D view region.
415+
// When cropped to 16:9, the 3D image fills the full window; otherwise
416+
// it is centered with the VR headset's aspect ratio.
417+
if (shouldCrop3DTo16_9) {
418+
DebugDraw::instance().Render(glm::vec2(0.0f, 0.0f), glm::vec2(windowSize.x, windowSize.y));
419+
}
420+
else {
421+
DebugDraw::instance().Render(glm::vec2(centerPos.x, centerPos.y), glm::vec2(squishedWindowSize.x, squishedWindowSize.y));
422+
}
412423
}
413424
}
414425
else {
@@ -420,6 +431,8 @@ void RND_Renderer::ImGuiOverlay::Render(long frameIdx, bool renderBackground) {
420431
VRManager::instance().Hooks->DrawDebugOverlays();
421432
}
422433

434+
435+
423436
if (((renderBackground && GetSettings().performanceOverlay == PerformanceOverlayMode::WINDOW_ONLY) || GetSettings().performanceOverlay == PerformanceOverlayMode::WINDOW_AND_VR) && !VRManager::instance().XR->m_isMenuOpen) {
424437
EntityDebugger::DrawFPSOverlay(renderer);
425438
}

0 commit comments

Comments
 (0)