diff --git a/CHANGES.md b/CHANGES.md index ddfd95c..1ef8c44 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,18 @@ +## Beta 1.1 +* Bumped module and MCM version number to 10 +* Added a compatibility patch for Immersive First Person View. +* Tweaked Improved Camera compatibility when on horseback. +* Remove dynamic casting for camera state method invocations, not really required. +* Config now reads fNearDistance, in case the player changes it - changing it isn't really advised though if you use Immersive First Person View (We check the expected near value against what it actually is, combined with a distance check, to try and figure out if we are in IFPV's hacked first person mode). +* Config now reads fMinCurrentZoom rather than use the hard coded value. +* Adjusted config defaults to no longer enable compat options - these should be opt-in. +* Using new raycasting method for the crosshair, dropped the jank custom intersection test. +* Fix arrow and magic projectiles spawning behind the player when using the archery patch with SSE Engine Fixes. +* Changed some of the interpolation math to FP64 to gain a bit more precision (might not end up being necessary). +* Switched to SKSE's ITimer over using naked qpc. +* Updated config defaults and added a new MCM option to restore default values. +* As some users have reported local space smoothing causing jitter, the local space smoothing rate now defaults to 1, making it opt-in. Jitter is mostly caused by a sub-60 frame rate, currently looking into options for correcting jitter in a later update. + ## Beta 1 * Bumped module and MCM version number to 9 * Promoted to beta. diff --git a/MCM/SmoothCamMCM.pex b/MCM/SmoothCamMCM.pex index a3543d3..9b37468 100644 Binary files a/MCM/SmoothCamMCM.pex and b/MCM/SmoothCamMCM.pex differ diff --git a/MCM/mcm.psc b/MCM/mcm.psc index 506e228..9bd96dd 100644 --- a/MCM/mcm.psc +++ b/MCM/mcm.psc @@ -10,6 +10,7 @@ Function SmoothCam_SetIntConfig(string member, int value) global native Function SmoothCam_SetStringConfig(string member, string value) global native Function SmoothCam_SetBoolConfig(string member, bool value) global native Function SmoothCam_SetFloatConfig(string member, float value) global native +Function SmoothCam_ResetConfig() global native int Function SmoothCam_GetIntConfig(string member) global native string Function SmoothCam_GetStringConfig(string member) global native @@ -90,6 +91,27 @@ endFunction } } +#constexpr_struct ResetSetting { + real_int ref = 0 + string displayName = "" + string desc = "" + + MACRO implControl = { + this->ref = AddToggleOption(this->displayName, false) + } + + MACRO implSelectHandler = { + if (ShowMessage("Are you sure? This will reset all settings to their default values.")) + SmoothCam_ResetConfig() + ShowMessage("Settings reset.") + endIf + } + + MACRO implDesc = { + SetInfoText(this->desc) + } +} + #constexpr_struct ListSetting { real_int ref = 0 string settingName = "" @@ -201,7 +223,7 @@ endFunction } ScriptMeta scriptMetaInfo -> { - version: 9 + version: 10 } ; Presets @@ -276,6 +298,17 @@ ToggleSetting disableDuringDialog -> { displayName: "Disable During Dialog" desc: "Disables SmoothCam when the dialog menu is open." } +ToggleSetting ifpvCompat -> { + settingName: "IFPVCompat" + displayName: "Immersive First Person View" + desc: "Enable compat fixes for Improved First Person View." +} + +; Reset +ResetSetting reset -> { + displayName: "Reset All Settings" + desc: "Set all settings back to their defual values." +} ; Following ToggleSetting interpEnabled -> { @@ -1370,6 +1403,9 @@ event OnPageReset(string a_page) IMPL_STRUCT_MACRO_INVOKE_GROUP(implControl, { icFirstPersonHorse, icFirstPersonDragon, icFirstPersonSitting }) + + AddHeaderOption("Immersive First Person View") + ifpvCompat->!implControl elseIf (a_page == " Following") AddHeaderOption("Interpolation") IMPL_STRUCT_MACRO_INVOKE_GROUP(implControl, { @@ -1406,7 +1442,7 @@ event OnPageReset(string a_page) AddHeaderOption("Misc") IMPL_STRUCT_MACRO_INVOKE_GROUP(implControl, { - shoulderSwapKey, swapDistanceClampXAxis, zoomMul, disableDeltaTime + shoulderSwapKey, swapDistanceClampXAxis, zoomMul, disableDeltaTime, reset }) elseIf (a_page == " Crosshair") AddHeaderOption("3D Crosshair Settings") @@ -1657,6 +1693,7 @@ endEvent event OnOptionSelect(int a_option) IMPL_IFCHAIN_MACRO_INVOKE(a_option, ref, implSelectHandler, { IMPL_ALL_IMPLS_OF_STRUCT(ToggleSetting), + IMPL_ALL_IMPLS_OF_STRUCT(ResetSetting), IMPL_ALL_IMPLS_OF_STRUCT(LoadPresetSetting) }) endEvent @@ -1693,6 +1730,7 @@ event OnOptionHighlight(int a_option) IMPL_IFCHAIN_MACRO_INVOKE(a_option, ref, implDesc, { IMPL_ALL_IMPLS_OF_STRUCT(SliderSetting), IMPL_ALL_IMPLS_OF_STRUCT(ToggleSetting), + IMPL_ALL_IMPLS_OF_STRUCT(ResetSetting), IMPL_ALL_IMPLS_OF_STRUCT(ListSetting), IMPL_ALL_IMPLS_OF_STRUCT(SavePresetSetting), IMPL_ALL_IMPLS_OF_STRUCT(LoadPresetSetting), diff --git a/SmoothCam/include/camera.h b/SmoothCam/include/camera.h index 198ef8a..a97f2b7 100644 --- a/SmoothCam/include/camera.h +++ b/SmoothCam/include/camera.h @@ -38,7 +38,6 @@ namespace Camera { const auto UNIT_FORWARD = glm::vec3(1.0f, 0.0f, 0.0f); const auto UNIT_RIGHT = glm::vec3(0.0f, 1.0f, 0.0f); const auto UNIT_UP = glm::vec3(0.0f, 0.0f, 1.0f); - constexpr auto SKYRIM_MIN_ZOOM_FRACTION = 0.2f; class SmoothCamera { public: @@ -50,6 +49,8 @@ namespace Camera { ~SmoothCamera() = default; public: + // Runs before the internal game camera logic + void PreGameUpdate(PlayerCharacter* player, CorrectedPlayerCamera* camera); // Selects the correct update method and positions the camera void UpdateCamera(PlayerCharacter* player, CorrectedPlayerCamera* camera); // Called when the player toggles the POV @@ -72,6 +73,9 @@ namespace Camera { const bool UpdateCameraPOVState(const PlayerCharacter* player, const CorrectedPlayerCamera* camera) noexcept; /// Camera state updates + // Check if the camera is near the player's head (for first person mods) + bool CameraNearHead(const PlayerCharacter* player, const CorrectedPlayerCamera* camere, float cutOff = 32.0f); + bool IFPV_InFirstPersonState(const PlayerCharacter* player, const CorrectedPlayerCamera* camera); // Returns the current camera state for use in selecting an update method const GameState::CameraState GetCurrentCameraState(const PlayerCharacter* player, const CorrectedPlayerCamera* camera); // Returns the current camera action state for use in the selected update method @@ -109,7 +113,7 @@ namespace Camera { // Returns the full local-space camera offset for the current player state glm::vec3 GetCurrentCameraOffset(PlayerCharacter* player, const CorrectedPlayerCamera* camera) const noexcept; // Returns the current smoothing scalar to use for the given distance to the player - float GetCurrentSmoothingScalar(const float distance, ScalarSelector method = ScalarSelector::Normal) const; + double GetCurrentSmoothingScalar(const float distance, ScalarSelector method = ScalarSelector::Normal) const; // Returns the user defined distance clamping vector pair std::tuple GetDistanceClamping() const noexcept; // Returns true if interpolation is allowed in the current state @@ -132,6 +136,8 @@ namespace Camera { void SetCrosshairEnabled(bool enabled) const; /// Camera getters + // Update the internal rotation + void UpdateInternalRotation(CorrectedPlayerCamera* camera) noexcept; // Returns the camera's yaw float GetCameraYawRotation(const CorrectedPlayerCamera* camera) const noexcept; // Returns the camera's pitch @@ -196,11 +202,17 @@ namespace Camera { CameraActionState lastActionState = CameraActionState::Unknown; mmath::NiMatrix44 worldToScreen = {}; + float lastNearPlane = 0.0f; + glm::vec3 gameInitialWorldPosition = { 0.0f, 0.0f, 0.0f }; + glm::vec3 gameLastActualPosition = { 0.0f, 0.0f, 0.0f }; glm::vec3 lastPosition = { 0.0f, 0.0f, 0.0f }; glm::vec3 currentPosition = { 0.0f, 0.0f, 0.0f }; glm::vec3 lastLocalPosition = { 0.0f, 0.0f, 0.0f }; glm::vec3 lastWorldPosition = { 0.0f, 0.0f, 0.0f }; + glm::vec2 currentRotation = { 0.0f, 0.0f }; + glm::quat currentQuat = glm::identity(); + template struct TransitionGroup { T lastPosition = {}; diff --git a/SmoothCam/include/config.h b/SmoothCam/include/config.h index 92d4eff..e6fba9e 100644 --- a/SmoothCam/include/config.h +++ b/SmoothCam/include/config.h @@ -75,6 +75,8 @@ namespace Config { typedef struct gameConf { float f3PArrowTiltUpAngle = 2.5f; float f3PBoltTiltUpAngle = 2.5f; + float fNearDistance = 15.0f; + float fMinCurrentZoom = -0.200000003; } GameConfig; typedef struct offsetGroup { @@ -127,9 +129,10 @@ namespace Config { // Comapt bool disableDuringDialog = false; - bool comaptIC_FirstPersonHorse = true; - bool comaptIC_FirstPersonDragon = true; - bool compatIC_FirstPersonSitting = true; + bool comaptIC_FirstPersonHorse = false; + bool comaptIC_FirstPersonDragon = false; + bool compatIC_FirstPersonSitting = false; + bool compatIFPV = false; // Primary interpolation bool enableInterp = true; @@ -142,15 +145,15 @@ namespace Config { // Separate local space interpolation bool separateLocalInterp = true; - ScalarMethods separateLocalScalar = ScalarMethods::CIRC_IN; - float localScalarRate = 0.75f; + ScalarMethods separateLocalScalar = ScalarMethods::EXP_IN; + float localScalarRate = 1.0f; // Separate Z bool separateZInterp = true; ScalarMethods separateZScalar = ScalarMethods::SINE_IN; float separateZMaxSmoothingDistance = 60.0f; - float separateZMinFollowRate = 0.15f; - float separateZMaxFollowRate = 0.4f; + float separateZMinFollowRate = 0.2f; + float separateZMaxFollowRate = 0.6f; // Offset interpolation bool enableOffsetInterpolation = true; @@ -168,7 +171,7 @@ namespace Config { float cameraDistanceClampXMax = 35.0f; bool cameraDistanceClampYEnable = true; float cameraDistanceClampYMin = -100.0f; - float cameraDistanceClampYMax = 20.0f; + float cameraDistanceClampYMax = 10.0f; bool cameraDistanceClampZEnable = true; float cameraDistanceClampZMin = -60.0f; float cameraDistanceClampZMax = 60.0f; @@ -198,6 +201,7 @@ namespace Config { void ReadConfigFile(); void SaveCurrentConfig(); UserConfig* GetCurrentConfig() noexcept; + void ResetConfig(); // Returns "" if ok, otherwise has an error message BSFixedString SaveConfigAsPreset(int slot, const BSFixedString& name); diff --git a/SmoothCam/include/havok/hkpCastCollector.h b/SmoothCam/include/havok/hkpCastCollector.h new file mode 100644 index 0000000..e8ef3b5 --- /dev/null +++ b/SmoothCam/include/havok/hkpCastCollector.h @@ -0,0 +1,177 @@ +#pragma once +using hkpShape = void; +using hkpCdBody = void; +using hkpCollidable = void; +using hkpShapeKey = uint32_t; + +typedef struct bhkShapeList { + hkpShape* shape; + uint64_t unk0; + void* shapeInfo; + bhkShapeList* next; + glm::vec3 unk1; + uint32_t flags; + uint32_t unk2; + uint32_t unk3; + uint32_t unk4; +} bhkShapeList; + +struct hkpRayHitResult { + glm::vec3 normal; + float hitFraction; + bhkShapeList* hit; +}; + +struct hkpAllCdPointTempResult { + glm::vec4 normal; + float hitFraction; +}; + +class hkpCastCollector { + public: + hkpCastCollector() { + results.reserve(64); + } + + inline void reset() { + results.clear(); + m_hitFraction = 0.0f; + m_earlyOutHitFraction = 1.0f; + } + + virtual void addRayHit(bhkShapeList* list, const hkpAllCdPointTempResult* hitInfo) { + hkpRayHitResult hitResult; + hitResult.hitFraction = hitInfo->hitFraction; + hitResult.normal = static_cast(hitInfo->normal); + + while (list) { + if (!list->next) break; + list = list->next; + } + + hitResult.hit = list; + if (hitResult.hit) { + const uint64_t m = 1ULL << static_cast(hitResult.hit->flags & 0x7F); + constexpr uint64_t filter = 0x40122716; + if ((m & filter) != 0) { + results.push_back(hitResult); + // We only want further hits to be closer than this + m_earlyOutHitFraction = hitResult.hitFraction; + } + } + } + + public: + enum { MAX_HIERARCHY_DEPTH = 8 }; + + float m_earlyOutHitFraction = 1.0f; //0x08 + uint32_t pad0; //0x0C + uint64_t pad1[2]; //0x10, 0x18 + float m_hitFraction = 0.0f; //0x20 + uint32_t unk0 = 0; //0x24 + hkpShapeKey shapeKey; //0x28 + uint32_t pad2; //0x2C + hkpShapeKey m_shapeKeys[MAX_HIERARCHY_DEPTH]; + uint32_t m_shapeKeyIndex = 0; + uint32_t pad3; + uint64_t unk1 = 0; + hkpCollidable* m_rootCollidable = nullptr; + uint64_t unk2 = 0; + + std::vector results; +}; + +typedef __declspec(align(16)) struct hkpRayCastInfo { + glm::vec4 start; // 0x0 + glm::vec4 unkVec; // 0x10 + bool unk0 = false; // 0x20 + + // auVar11 = subps(*(undefined *)(param_2 + 0x24),(undefined [16])0x0); + // auVar10 = ZEXT812(SUB168(auVar11,0) & 0x7ff ... + // uVar3 = movmskps(uVar3,CONCAT412(0xffffffff, ... + // if (((byte)uVar3 & 7) != 7) + // write unkVec.x SUB124(*hkpRayCastInfo >> 0x20, 0) + (float)hkpRayCastInfo[0x24] + // ... + // write unkVec.w SUB164(*hkpRayCastInfo >> 0x60, 0) + (float)hkpRayCastInfo[0x27] + // unk0 = 0 + uint32_t flags = 0; // 0x24 + + uint64_t unk1_0 = 0; + uint64_t unk1_1 = 0; + uint64_t unk1_2 = 0; + float unk2 = 1.0f; // 0x40 + glm::ivec3 unk3 = { -1, -1, -1 }; // 0x44 + uint64_t unk4_0 = 0; + uint64_t unk4_1 = 0; + uint64_t unk4_2 = 0; + uint64_t unk4_3 = 0; + uint32_t unk5 = 0; // 0x70 + uintptr_t unk6 = 0; + uint64_t unk7 = 0; // 0x80 + uintptr_t unk8 = 0; + + glm::vec4 end; // 0x90 + + // collector = *(longlong *)(param_2 + 0x2c); + // lVar2 = *(longlong *)(param_2 + 0x2e); + // if ((collector == 0) && (lVar2 == 0)) + // collector = *(longlong *)(param_2 + 0x2a); + // if (collector == 0) + // FUN_140a7b000_UnkTraceRelated(lVar5, hkpRayCastInfo, hkpRayCastInfo + 0xc); + // | + // v + // local_58[0] = &vtable.hkpSimpleWorldRayCaster; + // broadphase = broadphase = *(longlong **)(lVar5 + 0x88); + // filter = *(longlong *)(this + 0xd0); + // hkpSimpleWorldCastRayBroadphaseCache( + // local_58, + // broadphase, + // hkpRayCastInfo, + // filter, + // 0, // cache + // hkpRayCastInfo + 0xc + // ); + + // hkpSimpleWorldCastRayBroadphaseCache: + // (**(code **)(*broadphase + 200))(broadphase, hkpRayCastInfo, this, 0); + + // local_88[0] = &vtable.hkpWorldRayCaster; + // broadphase = *(longlong **)(lVar5 + 0x88); + // filter = *(longlong *)(lVar5 + 0xd0); + // cache = *(longlong *)(param_2 + 0x28); + // 140b28060:castRay(local_88, broadphase, hkpRayCastInfo, filter, cache, collector_00); + // broadphase->FUN_140b306f0 + // [ (**(code **)(*broadphase + 200))(broadphase, hkpRayCastInfo, this, 0) ] vtable.hkpWorldRayCaster->FUN_140b284f0 + // if ((*(byte *)(*(longlong *)(broadphase + 0xb0) + 0x10 + uVar9 * 0x18) & 1) == 0) { + // (**(code **)(*this + 8))( + // SUB168(auVar28,0), this, + // *(undefined8 *)(*(longlong *)(broadphase + 0xb0) + 0x10 + uVar9 * 0x18), + // (ulonglong)uVar34); [ hkpShape->FUN_140a59340:invokeCollector ] + // FUN_140a59340:invokeCollector(auVar28, this, broadphaseOffset, uVar34) + // (**(code **)*param_4)(param_4,param_3,&local_58); [ virtual addRayHit ] + + uint64_t unk9 = 0; // 0xA0 + hkpCastCollector* collector; // 0xA8 + uint64_t unk10 = 0; + uint64_t unk11 = 0; + bool unk12 = false; // 0xC0 + + // lVar22 = 3; + // plVar16 = 0xC0 + // plVar16 = plVar16 + 2; + // lVar22 += -1; + // while (lVar22 != 0) + uint64_t unk13[11] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +} hkpRayCastInfo; +static_assert(offsetof(hkpRayCastInfo, start.w) == 0xC); +static_assert(offsetof(hkpRayCastInfo, unk0) == 0x20); +static_assert(offsetof(hkpRayCastInfo, flags) == 0x24); +static_assert(offsetof(hkpRayCastInfo, unk2) == 0x40); +static_assert(offsetof(hkpRayCastInfo, unk5) == 0x70); +static_assert(offsetof(hkpRayCastInfo, unk7) == 0x80); +static_assert(offsetof(hkpRayCastInfo, end) == 0x90); +static_assert(offsetof(hkpRayCastInfo, end.w) == 0x9C); +static_assert(offsetof(hkpRayCastInfo, unk9) == 0xA0); +static_assert(offsetof(hkpRayCastInfo, collector) == 0xA8); +static_assert(offsetof(hkpRayCastInfo, unk12) == 0xC0); +static_assert(sizeof(hkpRayCastInfo) == 0x120); \ No newline at end of file diff --git a/SmoothCam/include/mmath.h b/SmoothCam/include/mmath.h index 7de41c6..74ec0d6 100644 --- a/SmoothCam/include/mmath.h +++ b/SmoothCam/include/mmath.h @@ -52,15 +52,6 @@ namespace mmath { void DecomposeToBasis(const glm::vec3& point, const glm::vec3& rotation, glm::vec3& forward, glm::vec3& right, glm::vec3& up, glm::vec3& coef) noexcept; - // Construct an AABB for an actor - AABB GetReferAABB(TESObjectREFR* ref); - AABB RotateAABB(const AABB& axisAligned, const NiMatrix33& mat) noexcept; - AABB GetActorAABB(Actor* actor); - - // Ray-AABB intersect - bool IntersectRayAABB(const glm::vec3& start, const glm::vec3& dir, const AABB& aabb, - glm::vec3& hitPos) noexcept; - glm::vec2 PointToScreen(const glm::vec3& point); template diff --git a/SmoothCam/include/pch.h b/SmoothCam/include/pch.h index 26c180a..14b0fa5 100644 --- a/SmoothCam/include/pch.h +++ b/SmoothCam/include/pch.h @@ -106,8 +106,11 @@ #include "addrlib/offsets.h" #ifdef _DEBUG +//# define DEBUG_DRAWING # include "profile.h" +# ifdef DEBUG_DRAWING # include "debug_drawing.h" +# endif #endif #include "basicdetour.h" diff --git a/SmoothCam/include/raycast.h b/SmoothCam/include/raycast.h index 5254d0b..6d02136 100644 --- a/SmoothCam/include/raycast.h +++ b/SmoothCam/include/raycast.h @@ -1,59 +1,7 @@ #pragma once +#include "havok/hkpCastCollector.h" namespace Raycast { - constexpr auto MaxActorIntersections = 16; - - // Thanks to CBPC SSE for revealing this handy object - // While not perfect, it works well enough for now - class AIProcessManager { - public: - static AIProcessManager* GetSingleton(); - - UInt8 unk000; // 008 - bool enableDetection; // 001 - bool unk002; // 002 - UInt8 unk003; // 003 - UInt32 unk004; // 004 - bool enableHighProcess; // 008 - bool enableLowProcess; // 009 - bool enableMiddleHighProcess; // 00A - bool enableMiddleLowProcess; // 00B - bool enableAISchedules; // 00C - UInt8 unk00D; // 00D - UInt8 unk00E; // 00E - UInt8 unk00F; // 00F - SInt32 numActorsInHighProcess; // 010 - UInt32 unk014[(0x30 - 0x014) / sizeof(UInt32)]; - tArray actorsHigh; // 030 - tArray actorsLow; // 048 - tArray actorsMiddleLow; // 060 - tArray actorsMiddleHigh; // 078 - UInt32 unk90[(0xF0 - 0x7C) / sizeof(UInt32)]; - tArray activeEffectShaders; // 108 - //mutable BSUniqueLock activeEffectShadersLock; // 120 - }; - - static_assert(offsetof(AIProcessManager, numActorsInHighProcess) >= 0x10, "Unk141F831B0::actorsHigh is too early!"); - static_assert(offsetof(AIProcessManager, numActorsInHighProcess) <= 0x10, "Unk141F831B0::actorsHigh is too late!"); - - static_assert(offsetof(AIProcessManager, actorsHigh) >= 0x030, "Unk141F831B0::actorsHigh is too early!"); - static_assert(offsetof(AIProcessManager, actorsHigh) <= 0x039, "Unk141F831B0::actorsHigh is too late!"); - - static_assert(offsetof(AIProcessManager, actorsLow) >= 0x048, "Unk141F831B0::actorsLow is too early!"); - static_assert(offsetof(AIProcessManager, actorsLow) <= 0x048, "Unk141F831B0::actorsLow is too late!"); - - static_assert(offsetof(AIProcessManager, actorsMiddleLow) >= 0x060, "Unk141F831B0::actorsMiddleLow is too early!"); - static_assert(offsetof(AIProcessManager, actorsMiddleLow) <= 0x060, "Unk141F831B0::actorsMiddleLow is too late!"); - - static_assert(offsetof(AIProcessManager, actorsMiddleHigh) >= 0x078, "Unk141F831B0::actorsMiddleHigh is too early!"); - static_assert(offsetof(AIProcessManager, actorsMiddleHigh) <= 0x078, "Unk141F831B0::actorsMiddleHigh is too late!"); - - static_assert(offsetof(AIProcessManager, activeEffectShaders) >= 0x108, "Unk141F831B0::activeEffectShaders is too early!"); - static_assert(offsetof(AIProcessManager, activeEffectShaders) <= 0x108, "Unk141F831B0::activeEffectShaders is too late!"); - - // These require more inspection - They all contain vtables of assorted physics shapes - // It would be nice to figure these out to the point that a TESObjectREFR could be - // extracted from them struct hkpGenericShapeData { intptr_t* unk; uint32_t shapeType; @@ -97,12 +45,8 @@ namespace Raycast { } RayResult; static_assert(sizeof(RayResult) == 128); - using ActorRayResults = std::array; - uint8_t IntersectRayAABBAllActorsIn(const tArray& list, ActorRayResults& results, const glm::vec3& start, - const glm::vec3& dir, float distance, uint8_t currentCount = 0); - RayResult InteresctRayAABBAllActors(const glm::vec3& start, const glm::vec3& end); - // Cast a ray from 'start' to 'end', returning the first thing it hits + // This variant is used by the camera to test with the world for clipping // Params: // glm::vec4 start: Starting position for the trace in world space // glm::vec4 end: End position for the trace in world space @@ -113,4 +57,16 @@ namespace Raycast { // A structure holding the results of the ray cast. // If the ray hit something, result.hit will be true. RayResult CastRay(glm::vec4 start, glm::vec4 end, float traceHullSize, bool intersectCharacters = false); + + // Cast a ray from 'start' to 'end', returning the first thing it hits + // This variant collides with pretty much any solid geometry + // Params: + // glm::vec4 start: Starting position for the trace in world space + // glm::vec4 end: End position for the trace in world space + // + // Returns: + // RayResult: + // A structure holding the results of the ray cast. + // If the ray hit something, result.hit will be true. + RayResult hkpCastRay(glm::vec4 start, glm::vec4 end); } \ No newline at end of file diff --git a/SmoothCam/include/skyrimSE/ThirdPersonState.h b/SmoothCam/include/skyrimSE/ThirdPersonState.h index e8e8d33..7f1300a 100644 --- a/SmoothCam/include/skyrimSE/ThirdPersonState.h +++ b/SmoothCam/include/skyrimSE/ThirdPersonState.h @@ -8,6 +8,9 @@ class CorrectedThirdPersonState : public TESCameraState virtual void Unk_09(void); // 0x48 virtual void Unk_0A(void); // 0x50 virtual void UpdateMode(bool weaponDrawn); // 0x58 + virtual void Unk01(); + virtual void Unk02(); + virtual void UpdateRotation(); PlayerInputHandler inputHandler; // 20 NiNode* cameraNode; // 30 diff --git a/SmoothCam/include/skyrimSE/bhkWorld.h b/SmoothCam/include/skyrimSE/bhkWorld.h index a08bc90..bbe82a0 100644 --- a/SmoothCam/include/skyrimSE/bhkWorld.h +++ b/SmoothCam/include/skyrimSE/bhkWorld.h @@ -1,5 +1,7 @@ #pragma once +struct hkpRayCastInfo; + class bhkWorld { public: virtual void unk1(); // 0x0 ==> FUN_140dac670 @@ -53,7 +55,7 @@ class bhkWorld { virtual void unk49(); // 0x180 ==> FUN_140ddc750 virtual void unk50(); // 0x188 ==> FUN_140ddc790 virtual void unk51(); // 0x190 ==> FUN_140da64e0 - virtual void unk52(); // 0x198 ==> FUN_140da7580 + virtual void CastRay(hkpRayCastInfo*); // 0x198 ==> FUN_140da7580 virtual void unk53(); // 0x1A0 ==> FUN_140da79c0 virtual void unk54(); // 0x1A8 ==> FUN_140da79f0 virtual void unk55(); // 0x1B0 ==> FUN_140da7af0 diff --git a/SmoothCam/source/arrow_fixes.cpp b/SmoothCam/source/arrow_fixes.cpp index 31beda2..c34a3d6 100644 --- a/SmoothCam/source/arrow_fixes.cpp +++ b/SmoothCam/source/arrow_fixes.cpp @@ -1,7 +1,7 @@ #include "arrow_fixes.h" #include "game_state.h" -#ifdef _DEBUG +#ifdef DEBUG_DRAWING SkyrimSE::ArrowProjectile* current; std::mutex segmentLock; std::vector> segments; @@ -16,19 +16,6 @@ void ArrowFixes::Draw() { } } -typedef uintptr_t(*UpdateTraceArrowProjectile)(SkyrimSE::ArrowProjectile*, NiPoint3*, NiPoint3*); -UpdateTraceArrowProjectile fnUpdateTraceArrowProjectile; -std::unique_ptr detUpdateTraceArrowProjectile; -uintptr_t mUpdateTraceArrowProjectile(SkyrimSE::ArrowProjectile* arrow, NiPoint3* to, NiPoint3* from) { - if (arrow->shooter == 0x00100000) { - std::lock_guard lock(segmentLock); - segments.push_back(std::make_tuple(glm::vec3{ from->x, from->y, from->z }, glm::vec3{ to->x, to->y, to->z })); - } - - auto ret = fnUpdateTraceArrowProjectile(arrow, to, from); - return ret; -} - typedef UInt32(*MaybeSpawnArrow)(uint32_t* arrowHandle, ArrowFixes::LaunchData* launchData, uintptr_t param_3, uintptr_t** param_4); MaybeSpawnArrow arrOrig; @@ -46,11 +33,61 @@ UInt32 mMaybeSpawnArrow(uint32_t* arrowHandle, ArrowFixes::LaunchData* launchDat std::lock_guard lock(segmentLock); segments.clear(); } + return ret; +} +typedef uintptr_t(*UpdateTraceArrowProjectile)(SkyrimSE::ArrowProjectile*, NiPoint3&, NiPoint3&); +UpdateTraceArrowProjectile fnUpdateTraceArrowProjectile; +std::unique_ptr detUpdateTraceArrowProjectile; +uintptr_t mUpdateTraceArrowProjectile(SkyrimSE::ArrowProjectile* arrow, NiPoint3& to, NiPoint3& from) { + if (arrow->shooter == 0x00100000) { + std::lock_guard lock(segmentLock); + segments.push_back(std::make_tuple(glm::vec3{ from.x, from.y, from.z }, glm::vec3{ to.x, to.y, to.z })); + } + auto ret = fnUpdateTraceArrowProjectile(arrow, to, from); return ret; } #endif +//FUN_14084b430:49866 +typedef void(*FactorCameraOffset)(CorrectedPlayerCamera* camera, NiPoint3& pos, bool fac); +FactorCameraOffset fnFactorCameraOffset; +std::unique_ptr detFactorCameraOffset; +void mFactorCameraOffset(CorrectedPlayerCamera* camera, NiPoint3& pos, bool fac) { + if (fac) { + fnFactorCameraOffset(camera, pos, fac); + return; + } + + auto tps = reinterpret_cast(camera->cameraStates[CorrectedPlayerCamera::kCameraState_ThirdPerson2]); + if (!tps) { + fnFactorCameraOffset(camera, pos, fac); + return; + } + + NiQuaternion quat; + NiMatrix33 mat; + + tps->UpdateRotation(); + typedef void(__thiscall CorrectedThirdPersonState::* GetRotation)(NiQuaternion&); + (tps->*reinterpret_cast(&CorrectedThirdPersonState::Unk_04))(quat); + + //1cfa50:makeMatrix33Qua + typedef void(*makeMatrix33Qua)(NiQuaternion& q, NiMatrix33& m); + Offsets::Get(15612)(quat, mat); + + NiPoint3 offsetActual; + + // SSE Engine Fixes will call GetEyeVector with factorCameraOffset = true + // We appear to screw this computation up as a side effect of correcting the interaction crosshair + // So, yeah. Just fix it here. + if (GameState::IsThirdPerson(camera) || GameState::IsInHorseCamera(camera) || GameState::IsInDragonCamera(camera)) + offsetActual = { 0, 0, 0 }; + else + offsetActual = tps->offsetVector; + pos = mat * offsetActual; +} + typedef void(*UpdateArrowFlightPath)(SkyrimSE::ArrowProjectile* arrow); UpdateArrowFlightPath fnUpdateArrowFlightPath; std::unique_ptr detArrowFlightPath; @@ -140,34 +177,34 @@ void mUpdateArrowFlightPath(SkyrimSE::ArrowProjectile* arrow) { bool ArrowFixes::Attach() { { - //140750150::UpdateArrowFlightPath - fnUpdateArrowFlightPath = Offsets::Get(42998); - detArrowFlightPath = std::make_unique( - reinterpret_cast(&fnUpdateArrowFlightPath), - mUpdateArrowFlightPath + //FUN_14084b430:FactorCameraOffset:GetEyeVector + fnFactorCameraOffset = Offsets::Get(49866); + detFactorCameraOffset = std::make_unique( + reinterpret_cast(&fnFactorCameraOffset), + mFactorCameraOffset ); - if (!detArrowFlightPath->Attach()) { + if (!detFactorCameraOffset->Attach()) { _ERROR("Failed to place detour on target function, this error is fatal."); FatalError(L"Failed to place detour on target function, this error is fatal."); } } -#ifdef _DEBUG { - //140751430::UpdateTraceArrowProjectile - fnUpdateTraceArrowProjectile = Offsets::Get(43008); - detUpdateTraceArrowProjectile = std::make_unique( - reinterpret_cast(&fnUpdateTraceArrowProjectile), - mUpdateTraceArrowProjectile + //140750150::UpdateArrowFlightPath + fnUpdateArrowFlightPath = Offsets::Get(42998); + detArrowFlightPath = std::make_unique( + reinterpret_cast(&fnUpdateArrowFlightPath), + mUpdateArrowFlightPath ); - if (!detUpdateTraceArrowProjectile->Attach()) { + if (!detArrowFlightPath->Attach()) { _ERROR("Failed to place detour on target function, this error is fatal."); FatalError(L"Failed to place detour on target function, this error is fatal."); } } +#ifdef DEBUG_DRAWING { arrOrig = Offsets::Get(42928); detMaybeArrow = std::make_unique( @@ -180,6 +217,20 @@ bool ArrowFixes::Attach() { FatalError(L"Failed to place detour on target function, this error is fatal."); } } + + { + //140751430::UpdateTraceArrowProjectile + fnUpdateTraceArrowProjectile = Offsets::Get(43008); + detUpdateTraceArrowProjectile = std::make_unique( + reinterpret_cast(&fnUpdateTraceArrowProjectile), + mUpdateTraceArrowProjectile + ); + + if (!detUpdateTraceArrowProjectile->Attach()) { + _ERROR("Failed to place detour on target function, this error is fatal."); + FatalError(L"Failed to place detour on target function, this error is fatal."); + } + } #endif return true; diff --git a/SmoothCam/source/camera.cpp b/SmoothCam/source/camera.cpp index 9e42678..214482e 100644 --- a/SmoothCam/source/camera.cpp +++ b/SmoothCam/source/camera.cpp @@ -1,11 +1,15 @@ #include "camera.h" #include "arrow_fixes.h" #ifdef _DEBUG +#ifdef DEBUG_DRAWING #include "debug_drawing.h" #endif +#endif +double CurTime() noexcept; +double CurQPC() noexcept; double GetFrameDelta() noexcept; -double GetTime() noexcept; +double GetQPCDelta() noexcept; Camera::SmoothCamera::SmoothCamera() noexcept : config(Config::GetCurrentConfig()) { cameraStates[static_cast(GameState::CameraState::ThirdPerson)] = @@ -48,6 +52,40 @@ const bool Camera::SmoothCamera::UpdateCameraPOVState(const PlayerCharacter* pla } #pragma region Camera state updates +// Check if the camera is near the player's head (for first person mods) +bool Camera::SmoothCamera::CameraNearHead(const PlayerCharacter* player, const CorrectedPlayerCamera* camere, float cutOff) { + // Grab the eye vector, if we can't find the head node the origin will be our fallback + NiPoint3 niOrigin, niNormal; + typedef void(__thiscall PlayerCharacter::* GetEyeVector)(NiPoint3& origin, NiPoint3& normal, bool factorCameraOffset) const; + (player->*reinterpret_cast(&PlayerCharacter::Unk_C2))(niOrigin, niNormal, false); + + BSFixedString name = "NPC Head [Head]"; + auto node = player->loadedState->node->GetObjectByName(&name.data); + if (node) { + niOrigin = node->m_worldTransform.pos; + } + + const auto dist = glm::distance( + glm::vec3{ + niOrigin.x, + niOrigin.y, + niOrigin.z + }, + gameLastActualPosition + ); + + return dist <= cutOff; +} + +// Immersive First Person patch +// Kind of in a bind here due to how IFPV patches the camera state transition, combined +// with the point during code execution when we run vs. they run - Just do a distance test +bool Camera::SmoothCamera::IFPV_InFirstPersonState(const PlayerCharacter* player, const CorrectedPlayerCamera* camera) { + // IFPV also changes the near plane which we can check to reduce false positives + // This is pretty damn hackey but without a better way to detect this we don't have much choice + return (CameraNearHead(player, camera) && lastNearPlane != Config::GetGameConfig()->fNearDistance); +} + // Returns the current camera state for use in selecting an update method const GameState::CameraState Camera::SmoothCamera::GetCurrentCameraState(const PlayerCharacter* player, const CorrectedPlayerCamera* camera) { GameState::CameraState newState = GameState::CameraState::Unknown; @@ -62,11 +100,28 @@ const GameState::CameraState Camera::SmoothCamera::GetCurrentCameraState(const P } newState = GameState::GetCameraState(player, camera); - if (newState == GameState::CameraState::Horseback && config->comaptIC_FirstPersonHorse) { + + const auto minZoom = Config::GetGameConfig()->fMinCurrentZoom; + + if (config->compatIFPV && (newState == GameState::CameraState::ThirdPerson || newState == GameState::CameraState::ThirdPersonCombat)) { + const auto tps = reinterpret_cast(camera->cameraState); + if (tps->cameraZoom == minZoom && tps->cameraLastZoom == minZoom) { + // IFPV + if (IFPV_InFirstPersonState(player, camera)) + newState = GameState::CameraState::FirstPerson; + } + } else if (config->compatIFPV && newState == GameState::CameraState::Horseback) { + // ditto + if (IFPV_InFirstPersonState(player, camera)) + newState = GameState::CameraState::FirstPerson; + } + + if (newState == GameState::CameraState::Horseback && config->comaptIC_FirstPersonHorse && !config->compatIFPV) { const auto tps = reinterpret_cast(camera->cameraState); if (tps) { - if ((tps->cameraZoom == -SKYRIM_MIN_ZOOM_FRACTION && tps->cameraLastZoom == -SKYRIM_MIN_ZOOM_FRACTION) || - currentActionState == CameraActionState::FirstPersonHorseback) + if ((tps->cameraZoom == minZoom && tps->cameraLastZoom == minZoom) || + currentActionState == CameraActionState::FirstPersonHorseback || + CameraNearHead(player, camera)) { if (povWasPressed) newState = GameState::CameraState::Horseback; @@ -81,7 +136,7 @@ const GameState::CameraState Camera::SmoothCamera::GetCurrentCameraState(const P } else if (newState == GameState::CameraState::Dragon && config->comaptIC_FirstPersonDragon) { const auto tps = reinterpret_cast(camera->cameraState); if (tps) { - if ((tps->cameraZoom == -SKYRIM_MIN_ZOOM_FRACTION && tps->cameraLastZoom == -SKYRIM_MIN_ZOOM_FRACTION) || + if ((tps->cameraZoom == minZoom && tps->cameraLastZoom == minZoom) || currentActionState == CameraActionState::FirstPersonDragon) { newState = GameState::CameraState::FirstPerson; @@ -173,28 +228,16 @@ void Camera::SmoothCamera::OnCameraStateTransition(const PlayerCharacter* player { switch (oldState) { case GameState::CameraState::ThirdPerson: { - if (cameraStates.at(static_cast(GameState::CameraState::ThirdPerson))) { - dynamic_cast( - cameraStates.at(static_cast(GameState::CameraState::ThirdPerson)).get() - )->OnEnd(player, camera); - break; - } + cameraStates.at(static_cast(GameState::CameraState::ThirdPerson))->OnEnd(player, camera); + break; } case GameState::CameraState::ThirdPersonCombat: { - if (cameraStates.at(static_cast(GameState::CameraState::ThirdPersonCombat))) { - dynamic_cast( - cameraStates.at(static_cast(GameState::CameraState::ThirdPersonCombat)).get() - )->OnEnd(player, camera); - break; - } + cameraStates.at(static_cast(GameState::CameraState::ThirdPersonCombat))->OnEnd(player, camera); + break; } case GameState::CameraState::Horseback: { - if (cameraStates.at(static_cast(GameState::CameraState::Horseback))) { - dynamic_cast( - cameraStates.at(static_cast(GameState::CameraState::Horseback)).get() - )->OnEnd(player, camera); - break; - } + cameraStates.at(static_cast(GameState::CameraState::Horseback))->OnEnd(player, camera); + break; } default: break; @@ -202,28 +245,16 @@ void Camera::SmoothCamera::OnCameraStateTransition(const PlayerCharacter* player switch (newState) { case GameState::CameraState::ThirdPerson: { - if (cameraStates.at(static_cast(GameState::CameraState::ThirdPerson))) { - dynamic_cast( - cameraStates.at(static_cast(GameState::CameraState::ThirdPerson)).get() - )->OnBegin(player, camera); - break; - } + cameraStates.at(static_cast(GameState::CameraState::ThirdPerson))->OnBegin(player, camera); + break; } case GameState::CameraState::ThirdPersonCombat: { - if (cameraStates.at(static_cast(GameState::CameraState::ThirdPersonCombat))) { - dynamic_cast( - cameraStates.at(static_cast(GameState::CameraState::ThirdPersonCombat)).get() - )->OnBegin(player, camera); - break; - } + cameraStates.at(static_cast(GameState::CameraState::ThirdPersonCombat))->OnBegin(player, camera); + break; } case GameState::CameraState::Horseback: { - if (cameraStates.at(static_cast(GameState::CameraState::Horseback))) { - dynamic_cast( - cameraStates.at(static_cast(GameState::CameraState::Horseback)).get() - )->OnBegin(player, camera); - break; - } + cameraStates.at(static_cast(GameState::CameraState::Horseback))->OnBegin(player, camera); + break; } default: break; @@ -493,11 +524,7 @@ void Camera::SmoothCamera::SetPosition(const glm::vec3& pos, const CorrectedPlay if (!mmath::IsValid(currentPosition)) { __debugbreak(); // Oops, go ahead and clear both - lastPosition = currentPosition = { - cameraNode->m_worldTransform.pos.x, - cameraNode->m_worldTransform.pos.y, - cameraNode->m_worldTransform.pos.z - }; + lastPosition = currentPosition = gameInitialWorldPosition; return; } #endif @@ -529,38 +556,46 @@ void Camera::SmoothCamera::UpdateInternalWorldToScreenMatrix(NiCamera* camera, f } // Returns the current smoothing scalar to use for the given distance to the player -float Camera::SmoothCamera::GetCurrentSmoothingScalar(const float distance, ScalarSelector method) const { +double Camera::SmoothCamera::GetCurrentSmoothingScalar(const float distance, ScalarSelector method) const { Config::ScalarMethods scalarMethod; + + // Work in FP64 here to eek out some more precision + // Avoid a divide-by-zero error by clamping to this lower bound + constexpr const double minZero = 0.000000000001; - float scalar = 1.0f; - float interpValue = 1.0f; - float remapped = 1.0f; + double scalar = 1.0; + double interpValue = 1.0; + double remapped = 1.0; if (method == ScalarSelector::SepZ) { - const auto max = config->separateZMaxSmoothingDistance; - scalar = glm::clamp(1.0f - ((max - distance) / max), 0.0f, 1.0f); - remapped = mmath::Remap(scalar, 0.0f, 1.0f, config->separateZMinFollowRate, config->separateZMaxFollowRate); + const auto max = static_cast(config->separateZMaxSmoothingDistance); + scalar = glm::clamp(glm::max(1.0 - (max - distance), minZero) / max, 0.0, 1.0); + remapped = mmath::Remap( + scalar, 0.0, 1.0, static_cast(config->separateZMinFollowRate), static_cast(config->separateZMaxFollowRate) + ); scalarMethod = config->separateZScalar; } else if (method == ScalarSelector::LocalSpace) { remapped = distance; scalarMethod = config->separateLocalScalar; } else { - const auto max = config->zoomMaxSmoothingDistance; - scalar = glm::clamp(1.0f - ((max - distance) / max), 0.0f, 1.0f); - remapped = mmath::Remap(scalar, 0.0f, 1.0f, config->minCameraFollowRate, config->maxCameraFollowRate); + const auto max = static_cast(config->zoomMaxSmoothingDistance); + scalar = glm::clamp(glm::max(1.0 - (max - distance), minZero) / max, 0.0, 1.0); + remapped = mmath::Remap( + scalar, 0.0, 1.0, static_cast(config->minCameraFollowRate), static_cast(config->maxCameraFollowRate) + ); scalarMethod = config->currentScalar; } if (!config->disableDeltaTime) { - const auto delta = GetFrameDelta(); - const auto fps = 1.0 / delta; - const auto mul = -fps * glm::log2(1.0f - remapped); - interpValue = static_cast(glm::clamp(1.0 - glm::exp2(-mul * delta), 0.0, 1.0)); + const double delta = glm::max(GetFrameDelta(), minZero); + const double fps = 1.0 / delta; + const double mul = -fps * glm::log2(1.0 - remapped); + interpValue = glm::clamp(1.0 - glm::exp2(-mul * delta), 0.0, 1.0); } else { interpValue = remapped; } - return mmath::RunScalarFunction(scalarMethod, interpValue); + return mmath::RunScalarFunction(scalarMethod, interpValue); } // Returns the user defined distance clamping vector pair @@ -647,9 +682,9 @@ void Camera::SmoothCamera::UpdateCrosshairPosition(PlayerCharacter* player, cons // @Note: I'm sure there is some way to make this perfect, but this is close enough float fac = 0.0f; - if (GameState::IsUsingCrossbow(*g_thePlayer)) { + if (GameState::IsUsingCrossbow(player)) { fac = glm::radians(Config::GetGameConfig()->f3PBoltTiltUpAngle) * 0.5f; - } else if (GameState::IsUsingBow(*g_thePlayer)) { + } else if (GameState::IsUsingBow(player)) { fac = glm::radians(Config::GetGameConfig()->f3PArrowTiltUpAngle) * 0.5f; } @@ -679,7 +714,7 @@ void Camera::SmoothCamera::UpdateCrosshairPosition(PlayerCharacter* player, cons constexpr auto rayLength = 6000.0f; // Range of most (all?) arrows auto origin = glm::vec4(niOrigin.x, niOrigin.y, niOrigin.z, 0.0f); auto ray = glm::vec4(niNormal.x, niNormal.y, niNormal.z, 0.0f) * rayLength; - const auto result = Raycast::CastRay(origin, origin + ray, 0.1f, true); + const auto result = Raycast::hkpCastRay(origin, origin + ray); auto port = NiRect(); auto menu = MenuManager::GetSingleton()->GetMenu(&UIStringHolder::GetSingleton()->hudMenu); @@ -720,7 +755,7 @@ void Camera::SmoothCamera::UpdateCrosshairPosition(PlayerCharacter* player, cons }; } -#ifdef _DEBUG +#ifdef DEBUG_DRAWING auto lineStart = mmath::PointToScreen(origin); auto lineEnd = mmath::PointToScreen(result.hit ? result.hitPos : origin + ray); DebugDrawing::Submit(DebugDrawing::DrawLine(lineStart, lineEnd, { 0.0f, 1.0f, 0.0f })); @@ -805,33 +840,56 @@ void Camera::SmoothCamera::SetCrosshairEnabled(bool enabled) const { #pragma endregion #pragma region Camera getters +void Camera::SmoothCamera::UpdateInternalRotation(CorrectedPlayerCamera* camera) noexcept { + const auto tps = reinterpret_cast(camera->cameraState); + if (!tps) return; + tps->UpdateRotation(); + currentQuat = glm::quat{ tps->rotation.m_fW, tps->rotation.m_fX, tps->rotation.m_fY, tps->rotation.m_fZ }; + + const auto pitch = glm::pitch(currentQuat); + const auto yaw = glm::roll(currentQuat); + currentRotation.x = pitch *-1; + currentRotation.y = yaw *-1; +} + // Returns the camera's pitch float Camera::SmoothCamera::GetCameraPitchRotation(const CorrectedPlayerCamera* camera) const noexcept { - const auto mat = camera->cameraNode->m_localTransform.rot; - const auto a = glm::clamp(-mat.data[2][1], -1.0f, 1.0f); - return glm::asin(a); + return currentRotation.x; } // Returns the camera's yaw float Camera::SmoothCamera::GetCameraYawRotation(const CorrectedPlayerCamera* camera) const noexcept { - const auto mtx = reinterpret_cast(camera->cameraNode->m_children.m_data[0])->m_worldTransform.rot; - if (mtx.data[0][0] <= 0.0000001f || mtx.data[2][2] <= 0.0000001f) { - auto ab = glm::atan(mtx.data[0][2], mtx.data[1][2]); - return ab - mmath::half_pi; - } - - return glm::atan(mtx.data[0][0], mtx.data[1][0]); - + return currentRotation.y; } // Returns the camera's current zoom level - Camera must extend ThirdPersonState float Camera::SmoothCamera::GetCameraZoomScalar(const CorrectedPlayerCamera* camera, uint16_t cameraState) const noexcept { const auto state = reinterpret_cast(camera->cameraStates[cameraState]); if (!state) return 0.0f; - return state->cameraZoom + SKYRIM_MIN_ZOOM_FRACTION; + return state->cameraZoom + (Config::GetGameConfig()->fMinCurrentZoom *-1); } #pragma endregion +// Use this method to snatch modifications done by mods that run after us +// Called before the internal game method runs which will overwrite most of that +void Camera::SmoothCamera::PreGameUpdate(PlayerCharacter* player, CorrectedPlayerCamera* camera) { + // Store the last actual position the game used for rendering + auto cameraNi = reinterpret_cast( + camera->cameraNode->m_children.m_size == 0 ? + nullptr : + camera->cameraNode->m_children.m_data[0] + ); + if (cameraNi) + gameLastActualPosition = { + cameraNi->m_worldTransform.pos.x, + cameraNi->m_worldTransform.pos.y, + cameraNi->m_worldTransform.pos.z + }; + + // Grab the last near plane value too, for IFPV compat checks + lastNearPlane = cameraNi->m_frustum.m_fNear; +} + // Selects the correct update method and positions the camera void Camera::SmoothCamera::UpdateCamera(PlayerCharacter* player, CorrectedPlayerCamera* camera) { #ifdef _DEBUG @@ -845,21 +903,23 @@ void Camera::SmoothCamera::UpdateCamera(PlayerCharacter* player, CorrectedPlayer auto cameraNode = camera->cameraNode; config = Config::GetCurrentConfig(); + gameInitialWorldPosition = { + cameraNode->m_worldTransform.pos.x, + cameraNode->m_worldTransform.pos.y, + cameraNode->m_worldTransform.pos.z + }; + // Update states & effects const auto pov = UpdateCameraPOVState(player, camera); const auto state = GetCurrentCameraState(player, camera); const auto actionState = GetCurrentCameraActionState(player, camera); offsetState.currentGroup = GetOffsetForState(actionState); const auto currentOffset = GetCurrentCameraOffset(player, camera); - const auto curTime = GetTime(); + const auto curTime = CurTime(); // Perform a bit of setup to smooth out camera loading if (!firstFrame) { - lastPosition = lastWorldPosition = currentPosition = { - cameraNode->m_worldTransform.pos.x, - cameraNode->m_worldTransform.pos.y, - cameraNode->m_worldTransform.pos.z - }; + lastPosition = lastWorldPosition = currentPosition = gameInitialWorldPosition; firstFrame = true; } @@ -891,41 +951,28 @@ void Camera::SmoothCamera::UpdateCamera(PlayerCharacter* player, CorrectedPlayer zoomTransitionState.currentPosition, offsetTransitionState.currentPosition.y }; - + // Save the camera position lastPosition = currentPosition; if (config->disableDuringDialog && dialogMenuOpen) { - lastPosition = lastWorldPosition = currentPosition = { - cameraNode->m_worldTransform.pos.x, - cameraNode->m_worldTransform.pos.y, - cameraNode->m_worldTransform.pos.z - }; + lastPosition = lastWorldPosition = currentPosition = gameInitialWorldPosition; } else { switch (state) { case GameState::CameraState::ThirdPerson: { - if (cameraStates.at(static_cast(GameState::CameraState::ThirdPerson))) { - dynamic_cast( - cameraStates.at(static_cast(GameState::CameraState::ThirdPerson)).get() - )->Update(player, camera); - break; - } + UpdateInternalRotation(camera); + cameraStates.at(static_cast(GameState::CameraState::ThirdPerson))->Update(player, camera); + break; } case GameState::CameraState::ThirdPersonCombat: { - if (cameraStates.at(static_cast(GameState::CameraState::ThirdPersonCombat))) { - dynamic_cast( - cameraStates.at(static_cast(GameState::CameraState::ThirdPersonCombat)).get() - )->Update(player, camera); - break; - } + UpdateInternalRotation(camera); + cameraStates.at(static_cast(GameState::CameraState::ThirdPersonCombat))->Update(player, camera); + break; } case GameState::CameraState::Horseback: { - if (cameraStates.at(static_cast(GameState::CameraState::Horseback))) { - dynamic_cast( - cameraStates.at(static_cast(GameState::CameraState::Horseback)).get() - )->Update(player, camera); - break; - } + UpdateInternalRotation(camera); + cameraStates.at(static_cast(GameState::CameraState::Horseback))->Update(player, camera); + break; } // Here just for my own reference that these are unused (for now) @@ -941,15 +988,12 @@ void Camera::SmoothCamera::UpdateCamera(PlayerCharacter* player, CorrectedPlayer case GameState::CameraState::Bleedout: case GameState::CameraState::Dragon: case GameState::CameraState::Unknown: - default: { + default: + { SetCrosshairEnabled(true); CenterCrosshair(); SetCrosshairSize({ baseCrosshairData.xScale, baseCrosshairData.yScale }); - lastPosition = lastWorldPosition = currentPosition = { - cameraNode->m_worldTransform.pos.x, - cameraNode->m_worldTransform.pos.y, - cameraNode->m_worldTransform.pos.z - }; + lastPosition = lastWorldPosition = currentPosition = gameInitialWorldPosition; break; } } diff --git a/SmoothCam/source/camera_state.cpp b/SmoothCam/source/camera_state.cpp index 2f695fc..0f453de 100644 --- a/SmoothCam/source/camera_state.cpp +++ b/SmoothCam/source/camera_state.cpp @@ -1,5 +1,6 @@ #include "camera_state.h" #include "camera.h" +#include "raycast.h" Camera::State::BaseCameraState::BaseCameraState(Camera::SmoothCamera* camera) noexcept : camera(camera) {} @@ -194,13 +195,13 @@ glm::vec3 Camera::State::BaseCameraState::UpdateInterpolatedLocalPosition(Player return rot; } - const auto pos = mmath::Interpolate( + const auto pos = mmath::Interpolate( camera->lastLocalPosition, rot, camera->GetCurrentSmoothingScalar( camera->config->localScalarRate, ScalarSelector::LocalSpace ) ); - StoreLastLocalPosition(pos); + StoreLastLocalPosition(static_cast(pos)); return pos; } @@ -215,21 +216,21 @@ glm::vec3 Camera::State::BaseCameraState::UpdateInterpolatedWorldPosition(Player } if (GetConfig()->separateZInterp) { - const auto xy = mmath::Interpolate( + const auto xy = mmath::Interpolate( camera->lastWorldPosition, pos, camera->GetCurrentSmoothingScalar(distance) ); - const auto z = mmath::Interpolate( + const auto z = mmath::Interpolate( camera->lastWorldPosition, pos, camera->GetCurrentSmoothingScalar(distance, ScalarSelector::SepZ) ); return { xy.x, xy.y, z.z }; } else { - const auto ret = mmath::Interpolate( + const auto ret = mmath::Interpolate( camera->lastWorldPosition, pos, camera->GetCurrentSmoothingScalar(distance) ); - return ret; + return static_cast(ret); } } diff --git a/SmoothCam/source/config.cpp b/SmoothCam/source/config.cpp index 5952320..61e5251 100644 --- a/SmoothCam/source/config.cpp +++ b/SmoothCam/source/config.cpp @@ -75,6 +75,7 @@ void Config::to_json(json& j, const UserConfig& obj) { CREATE_JSON_VALUE(obj, comaptIC_FirstPersonHorse), CREATE_JSON_VALUE(obj, comaptIC_FirstPersonDragon), CREATE_JSON_VALUE(obj, compatIC_FirstPersonSitting), + CREATE_JSON_VALUE(obj, compatIFPV), CREATE_JSON_VALUE(obj, minCameraFollowDistance), CREATE_JSON_VALUE(obj, minCameraFollowRate), CREATE_JSON_VALUE(obj, maxCameraFollowRate), @@ -134,6 +135,7 @@ void Config::from_json(const json& j, UserConfig& obj) { VALUE_FROM_JSON(obj, comaptIC_FirstPersonHorse) VALUE_FROM_JSON(obj, comaptIC_FirstPersonDragon) VALUE_FROM_JSON(obj, compatIC_FirstPersonSitting) + VALUE_FROM_JSON(obj, compatIFPV) VALUE_FROM_JSON(obj, minCameraFollowDistance) VALUE_FROM_JSON(obj, minCameraFollowRate) VALUE_FROM_JSON(obj, maxCameraFollowRate) @@ -221,6 +223,16 @@ void Config::ReadConfigFile() { wchar_t* end; gameConfig.f3PBoltTiltUpAngle = std::wcstof(buf, &end); } + + if (GetPrivateProfileString(L"Display", L"fNearDistance", L"15.0", buf, 16, inipath.c_str()) != 0) { + wchar_t* end; + gameConfig.fNearDistance = std::wcstof(buf, &end); + } + + if (GetPrivateProfileString(L"Camera", L"fMinCurrentZoom", L"-0.200000003", buf, 16, inipath.c_str()) != 0) { + wchar_t* end; + gameConfig.fMinCurrentZoom = std::wcstof(buf, &end); + } } currentConfig = cfg; @@ -236,6 +248,11 @@ Config::UserConfig* Config::GetCurrentConfig() noexcept { return ¤tConfig; } +void Config::ResetConfig() { + currentConfig = {}; + SaveCurrentConfig(); +} + BSFixedString Config::SaveConfigAsPreset(int slot, const BSFixedString& name) { if (slot >= MaxPresetSlots) { return { "ERROR: Preset index out of range" }; diff --git a/SmoothCam/source/detours.cpp b/SmoothCam/source/detours.cpp index f51d604..8b0a4fb 100644 --- a/SmoothCam/source/detours.cpp +++ b/SmoothCam/source/detours.cpp @@ -2,13 +2,24 @@ #include "camera.h" #include "arrow_fixes.h" +#include + static PLH::VFuncMap origVFuncs_PlayerInput; static PLH::VFuncMap origVFuncs_MenuOpenClose; std::weak_ptr g_theCamera; + +static ITimer timer; double curFrame = 0.0; double lastFrame = 0.0; -double GetTime() noexcept { +double curQPC = 0.0; +double lastQPC = 0.0; + +static double GetTime() noexcept { + return timer.GetElapsedTime(); +} + +static double GetQPC() noexcept { LARGE_INTEGER f, i; if (QueryPerformanceCounter(&i) && QueryPerformanceFrequency(&f)) { auto frequency = 1.0 / static_cast(f.QuadPart); @@ -21,23 +32,40 @@ void StepFrameTime() noexcept { lastFrame = curFrame; curFrame = GetTime(); -#ifdef _DEBUG + lastQPC = curQPC; + curQPC = GetQPC(); + +#ifdef DEBUG_DRAWING ArrowFixes::Draw(); #endif } +double CurTime() noexcept { + return curFrame; +} + +double CurQPC() noexcept { + return curQPC; +} + double GetFrameDelta() noexcept { - return static_cast(curFrame - lastFrame); + return curFrame - lastFrame; +} + +double GetQPCDelta() noexcept { + return curQPC - lastQPC; } #define CAMERA_UPDATE_DETOUR_IMPL(name) \ static PLH::VFuncMap origVFuncs_##name##; \ void __fastcall mCameraStateUpdate##name##(TESCameraState* pThis, void* unk) { \ - reinterpret_cast(origVFuncs_##name##.at(3))(pThis, unk); \ StepFrameTime(); \ + std::shared_ptr lockedPtr; \ auto player = *g_thePlayer; \ auto camera = CorrectedPlayerCamera::GetSingleton(); \ - std::shared_ptr lockedPtr; \ + if ((camera && player); lockedPtr = g_theCamera.lock(), lockedPtr != nullptr) \ + lockedPtr->PreGameUpdate(player, camera); \ + reinterpret_cast(origVFuncs_##name##.at(3))(pThis, unk); \ if ((camera && player); lockedPtr = g_theCamera.lock(), lockedPtr != nullptr) \ lockedPtr->UpdateCamera(player, camera); \ } @@ -108,6 +136,7 @@ EventResult __fastcall mMenuOpenCloseHandler(uintptr_t pThis, MenuOpenCloseEvent bool Detours::Attach(std::shared_ptr theCamera) { g_theCamera = theCamera; + timer.Start(); { PLH::VFuncSwapHook playerInputHooks( diff --git a/SmoothCam/source/main.cpp b/SmoothCam/source/main.cpp index 1c4069b..468d369 100644 --- a/SmoothCam/source/main.cpp +++ b/SmoothCam/source/main.cpp @@ -24,7 +24,7 @@ void SKSEMessageHandler(SKSEMessagingInterface::Message* message) { } break; } -#ifdef _DEBUG +#ifdef DEBUG_DRAWING case SKSEMessagingInterface::kMessage_InputLoaded: { DebugDrawing::DetourD3D11(); } @@ -57,7 +57,7 @@ extern "C" { info->infoVersion = PluginInfo::kInfoVersion; info->name = "SmoothCam"; - info->version = 9; + info->version = 10; g_pluginHandle = skse->GetPluginHandle(); @@ -66,7 +66,7 @@ extern "C" { } if (skse->runtimeVersion != RUNTIME_VERSION_1_5_97) { - _WARNING("This module was compiled for skse 1.5.97, you are running an unsupported verion. You may experience crashes or other strange issues."); + _WARNING("This module was compiled for skse 1.5.97, you are running an unsupported version. You may experience crashes or other strange issues."); } return true; diff --git a/SmoothCam/source/mmath.cpp b/SmoothCam/source/mmath.cpp index c495d2f..ef0128e 100644 --- a/SmoothCam/source/mmath.cpp +++ b/SmoothCam/source/mmath.cpp @@ -123,105 +123,6 @@ void mmath::DecomposeToBasis(const glm::vec3& point, const glm::vec3& rotation, }; } -mmath::AABB mmath::GetReferAABB(TESObjectREFR* ref) { - const auto mins = (ref->*reinterpret_cast(&TESObjectREFR::Unk_73))(); - const auto maxs = (ref->*reinterpret_cast(&TESObjectREFR::Unk_74))(); - - auto center = (mins + maxs) * 0.5f; - auto extent = (maxs - mins) * 0.5f; - auto mmins = center - extent; - auto mmaxs = center + extent; - - return { - {mmins.x, mmins.y, mmins.z}, - {mmaxs.x, mmaxs.y, mmaxs.z} - }; -} - -NiNode* FindByName(NiNode* root, const char* name) { - if (!root->m_children.m_data) return nullptr; - - for (auto i = 0; i < root->m_children.m_size; i++) { - NiNode* node = DYNAMIC_CAST(root->m_children.m_data[i], NiAVObject, NiNode); - if (!node || !node->m_name) continue; - if (strcmp(node->m_name, name) == 0) return node; - node = FindByName(node, name); - if (node) return node; - } - - return nullptr; -} - -mmath::AABB mmath::GetActorAABB(Actor* actor) { - // @Note: currently having issues with proper AABB rotation - // Additionally, some non-human actors need to be handled differently - if (!actor->loadedState || !actor->loadedState->node) return {}; - auto parent = actor->loadedState->node->m_parent; - - auto comName = "NPC Root [Root]"; - auto root = FindByName(actor->loadedState->node, comName); - if (!root) return {}; - - auto common = root->m_children.m_data[0]; - if (!common) return {}; - - const auto aabb = GetReferAABB(actor); - const auto rot = common->m_worldTransform.rot; - return mmath::RotateAABB(aabb, rot) + actor->pos; -} - -mmath::AABB mmath::RotateAABB(const AABB& aabb, const NiMatrix33& mat) noexcept { - auto blf = mat * NiPoint3{ aabb.mins.x, aabb.mins.y, aabb.mins.z }; - auto brf = mat * NiPoint3{ aabb.maxs.x, aabb.mins.y, aabb.mins.z }; - auto blb = mat * NiPoint3{ aabb.mins.x, aabb.maxs.y, aabb.mins.z }; - auto brb = mat * NiPoint3{ aabb.maxs.x, aabb.maxs.y, aabb.mins.z }; - auto tlf = mat * NiPoint3{ aabb.mins.x, aabb.mins.y, aabb.maxs.z }; - auto trf = mat * NiPoint3{ aabb.maxs.x, aabb.mins.y, aabb.maxs.z }; - auto tlb = mat * NiPoint3{ aabb.mins.x, aabb.maxs.y, aabb.maxs.z }; - auto trb = mat * NiPoint3{ aabb.maxs.x, aabb.maxs.y, aabb.maxs.z }; - - return { - { - std::min({blf.x, brf.x, blb.x, brb.x, tlf.x, trf.x, tlb.x, trb.x}), - std::min({blf.y, brf.y, blb.y, brb.y, tlf.y, trf.y, tlb.y, trb.y}), - std::min({blf.z, brf.z, blb.z, brb.z, tlf.z, trf.z, tlb.z, trb.z}) - }, - { - std::max({blf.x, brf.x, blb.x, brb.x, tlf.x, trf.x, tlb.x, trb.x}), - std::max({blf.y, brf.y, blb.y, brb.y, tlf.y, trf.y, tlb.y, trb.y}), - std::max({blf.z, brf.z, blb.z, brb.z, tlf.z, trf.z, tlb.z, trb.z}) - } - }; -} - -bool mmath::IntersectRayAABB(const glm::vec3& start, const glm::vec3& dir, const AABB& aabb, - glm::vec3& hitPos) noexcept -{ - float lo = std::numeric_limits::min(); - float hi = std::numeric_limits::max(); - - for (auto i = 0; i < 3; i++) { - auto dimLo = (aabb.mins[i] - start[i]) / dir[i]; - auto dimHi = (aabb.maxs[i] - start[i]) / dir[i]; - if (dimLo > dimHi) std::swap(dimLo, dimHi); - - if (dimHi < lo || dimLo > hi) - return false; - - if (dimLo > lo) lo = dimLo; - if (dimHi < hi) hi = dimHi; - } - - if (lo > hi) return false; - hitPos = { - start.x + dir.x * lo, - start.y + dir.y * lo, - start.z + dir.z * lo - }; - - return true; -} - glm::vec2 mmath::PointToScreen(const glm::vec3& point) { auto port = NiRect(); port.m_left = -1.0f; diff --git a/SmoothCam/source/papyrus.cpp b/SmoothCam/source/papyrus.cpp index 9818ce2..6146fc3 100644 --- a/SmoothCam/source/papyrus.cpp +++ b/SmoothCam/source/papyrus.cpp @@ -60,6 +60,7 @@ const std::unordered_map> boolGetter IMPL_GETTER("FirstPersonHorse", comaptIC_FirstPersonHorse) IMPL_GETTER("FirstPersonDragon", comaptIC_FirstPersonDragon) IMPL_GETTER("FirstPersonSitting", compatIC_FirstPersonSitting) + IMPL_GETTER("IFPVCompat", compatIFPV) IMPL_GETTER("InterpolationEnabled", enableInterp) IMPL_GETTER("SeparateLocalInterpolation", separateLocalInterp) IMPL_GETTER("DisableDeltaTime", disableDeltaTime) @@ -247,6 +248,7 @@ const std::unordered_map> boolSetter IMPL_SETTER("FirstPersonHorse", comaptIC_FirstPersonHorse, bool) IMPL_SETTER("FirstPersonDragon", comaptIC_FirstPersonDragon, bool) IMPL_SETTER("FirstPersonSitting", compatIC_FirstPersonSitting, bool) + IMPL_SETTER("IFPVCompat", compatIFPV, bool) IMPL_SETTER("InterpolationEnabled", enableInterp, bool) IMPL_SETTER("SeparateLocalInterpolation", separateLocalInterp, bool) IMPL_SETTER("DisableDeltaTime", disableDeltaTime, bool) @@ -581,4 +583,15 @@ void PapyrusBindings::Bind(VMClassRegistry* registry) { registry ) ); + + registry->RegisterFunction( + new NativeFunction0( + "SmoothCam_ResetConfig", + ScriptClassName, + [](StaticFunctionTag* thisInput) { + Config::ResetConfig(); + }, + registry + ) + ); } \ No newline at end of file diff --git a/SmoothCam/source/raycast.cpp b/SmoothCam/source/raycast.cpp index 31245a1..97417c4 100644 --- a/SmoothCam/source/raycast.cpp +++ b/SmoothCam/source/raycast.cpp @@ -1,187 +1,14 @@ -#include +#include "raycast.h" namespace { typedef bool(__fastcall* RayCastFunType)( UnkPhysicsHolder* physics, bhkWorld* world, glm::vec4& rayStart, glm::vec4& rayEnd, uint32_t* rayResultInfo, Character** hitCharacter, float traceHullSize ); - - constexpr auto ThreadActorIntersectionAtCount = 64; -} - -#ifdef _DEBUG -void DrawAABB(const mmath::AABB& aabb, bool hit) { - const auto hitColor = glm::vec3{ 0.0f, 1.0f, 0.0f }; - const auto missColor = glm::vec3{ 1.0f, 0.0f, 0.0f }; - const auto color = hit ? hitColor : missColor; - const auto blf = mmath::PointToScreen(aabb.mins); - const auto brf = mmath::PointToScreen({ aabb.maxs.x, aabb.mins.y, aabb.mins.z }); - const auto blb = mmath::PointToScreen({ aabb.mins.x, aabb.maxs.y, aabb.mins.z }); - const auto brb = mmath::PointToScreen({ aabb.maxs.x, aabb.maxs.y, aabb.mins.z }); - const auto tlf = mmath::PointToScreen({ aabb.mins.x, aabb.mins.y, aabb.maxs.z }); - const auto trf = mmath::PointToScreen({ aabb.maxs.x, aabb.mins.y, aabb.maxs.z }); - const auto tlb = mmath::PointToScreen({ aabb.mins.x, aabb.maxs.y, aabb.maxs.z }); - const auto trb = mmath::PointToScreen(aabb.maxs); - DebugDrawing::Submit(DebugDrawing::DrawBox(DebugDrawing::DrawBox::BoxPoints({ - blf, brf, blb, brb, - tlf, trf, tlb, trb - }), color)); -} -#endif - -Raycast::AIProcessManager* Raycast::AIProcessManager::GetSingleton() { - static auto ofs = Offsets::Get(514167); - return *ofs; -} - -// Perform a ray-AABB intersection test with all actors in the given list, return the hits -uint8_t Raycast::IntersectRayAABBAllActorsIn(const tArray& list, ActorRayResults& results, - const glm::vec3& start, const glm::vec3& dir, float distance, uint8_t currentCount) -{ - uint8_t hitCount = 0; -#ifdef _DEBUG - Profiler prof; -#endif - - // Lazy effort to multithread the tests when dealing with a large amount of actors - if (list.count >= ThreadActorIntersectionAtCount) { - std::atomic_uint counter = currentCount; - - std::for_each( - std::execution::par_unseq, - list.entries, - list.entries + list.count, - [&counter, &results, &start, &dir, &distance](UInt32 refID) { - if (counter >= Raycast::MaxActorIntersections) return; - - NiPointer ref; - (*LookupREFRByHandle)(refID, ref); - if (!ref) return; - auto actor = DYNAMIC_CAST(ref, TESObjectREFR, Actor); - if (!actor) return; - - const auto bits = std::bitset<32>(actor->flags1); - constexpr const auto setOnDeath = 23; - if (bits[setOnDeath]) return; - - actor->IncRef(); - { - const auto pos = glm::vec3(actor->pos.x, actor->pos.y, actor->pos.z); - if (glm::length2(static_cast(start) - pos) <= distance) { - const auto aabb = mmath::GetActorAABB(actor); - glm::vec3 hit; - if (mmath::IntersectRayAABB(start, dir, aabb, hit)) { - Raycast::RayResult res; - res.hit = true; - res.hitPos = glm::vec4(hit, 0.0f); - res.rayLength = glm::length(hit - start); - res.hitCharacter = reinterpret_cast(actor); - const auto old = counter.fetch_add(1, std::memory_order::memory_order_relaxed); - if (old < ThreadActorIntersectionAtCount) - results[old] = res; -#ifdef _DEBUG - DrawAABB(aabb, true); -#endif - } -#ifdef _DEBUG - else - DrawAABB(aabb, false); -#endif - } - } - actor->DecRef(); - } - ); - - hitCount = counter; - } else { - uint32_t counter = currentCount; - - std::for_each( - std::execution::seq, - list.entries, - list.entries + list.count, - [&counter, &results, &start, &dir, &distance](UInt32 refID) { - if (counter >= Raycast::MaxActorIntersections) return; - - NiPointer ref; - (*LookupREFRByHandle)(refID, ref); - if (!ref) return; - auto actor = DYNAMIC_CAST(ref, TESObjectREFR, Actor); - if (!actor) return; - - const auto bits = std::bitset<32>(actor->flags1); - constexpr const auto setOnDeath = 23; - if (bits[setOnDeath]) return; - - actor->IncRef(); - { - const auto pos = glm::vec3(actor->pos.x, actor->pos.y, actor->pos.z); - if (glm::length2(static_cast(start) - pos) <= distance) { - const auto aabb = mmath::GetActorAABB(actor); - glm::vec3 hit; - if (mmath::IntersectRayAABB(start, dir, aabb, hit)) { - Raycast::RayResult res; - res.hit = true; - res.hitPos = glm::vec4(hit, 0.0f); - res.rayLength = glm::length(hit - start); - res.hitCharacter = reinterpret_cast(actor); - results[counter] = res; - counter++; -#ifdef _DEBUG - DrawAABB(aabb, true); -#endif - } -#ifdef _DEBUG - else - DrawAABB(aabb, false); -#endif - } - } - actor->DecRef(); - } - ); - - hitCount = counter; - } - -#ifdef _DEBUG - const auto snap = prof.Snap(); - int bp = 0; -#endif - - return hitCount; -} - -Raycast::RayResult Raycast::InteresctRayAABBAllActors(const glm::vec3& start, const glm::vec3& end) { - const auto aiMgr = AIProcessManager::GetSingleton(); - const auto rayDistance = glm::length2(start - static_cast(end)); - const auto dir = glm::normalize(static_cast(end) - start); - std::array hitActors; - - // Intersect with all actors in the high process - // @Note: With debug drawing on, I can see actors right next to the player that aren't in this list - // until they start a new AI activity - for now this will have to do - const auto hitCount = IntersectRayAABBAllActorsIn(aiMgr->actorsHigh, hitActors, start, dir, rayDistance); - - Raycast::RayResult closestHit; - for (auto i = 0; i < hitCount; i++) { - auto hit = hitActors[i]; - if (closestHit.hit) { - if (closestHit.rayLength > hit.rayLength) - closestHit = hit; - } else { - closestHit = hit; - } - } - - return closestHit; } Raycast::RayResult Raycast::CastRay(glm::vec4 start, glm::vec4 end, float traceHullSize, bool intersectCharacters) { RayResult res; - RayResult actorRes; - #ifdef _DEBUG // A saftey net for my own bad code - if the engine EVER gets a nan or inf float // shit WILL hit the fan in the audio & rendering systems (but mostly the audio system) @@ -197,11 +24,6 @@ Raycast::RayResult Raycast::CastRay(glm::vec4 start, glm::vec4 end, float traceH ply->handleRefObject.IncRef(); { - // Homebrew intersection with actors - // This is pretty lame, need to find an engine method for proper ray-actor testing - if (intersectCharacters) - actorRes = InteresctRayAABBAllActors(start, end); - auto physicsWorld = Physics::GetWorld(ply->parentCell); if (physicsWorld) { res.hit = Offsets::Get(32270)( // 0x4f45f0 @@ -218,11 +40,92 @@ Raycast::RayResult Raycast::CastRay(glm::vec4 start, glm::vec4 end, float traceH res.rayLength = glm::length(static_cast(res.hitPos) - static_cast(start)); } - // Check if we hit an actor and if the hit is closer to us - if (intersectCharacters && actorRes.hit && res.hit) { - if (actorRes.rayLength < res.rayLength) - return actorRes; // Hit an actor + return res; +} + +hkpCastCollector* getCastCollector() { + static hkpCastCollector collector = hkpCastCollector(); + return &collector; +} + +Raycast::RayResult Raycast::hkpCastRay(glm::vec4 start, glm::vec4 end) { +#ifdef _DEBUG + if (!mmath::IsValid(start) || !mmath::IsValid(end)) { + __debugbreak(); + return {}; } +#endif - return res; + constexpr auto hkpScale = 0.0142875f; + const auto dif = end - start; + + hkpRayCastInfo info; + info.start = start * hkpScale; + info.end = dif * hkpScale; + info.collector = getCastCollector(); + info.collector->reset(); + + auto ply = *g_thePlayer; + ply->handleRefObject.IncRef(); + { + auto physicsWorld = Physics::GetWorld(ply->parentCell); + if (physicsWorld) { + physicsWorld->CastRay(&info); + } + } + ply->handleRefObject.DecRef(); + + hkpRayHitResult best = {}; + best.hitFraction = 1.0f; + glm::vec4 bestPos = {}; + + for (auto& hit : info.collector->results) { + const auto pos = (dif * hit.hitFraction) + start; + if (best.hit == nullptr) { + best = hit; + bestPos = pos; + continue; + } + + if (hit.hitFraction < best.hitFraction) { + best = hit; + bestPos = pos; + } + } + + RayResult result; + result.hitPos = bestPos; + result.rayLength = glm::length(bestPos - start); + + // FUN_1404f45f0 <32270> + // 140dad060:GetAVObjectFromHavok <76160> + // 1402945e0:ExtractCharacterFromTraceRes <19323> + + /* + MOV EDI,dword ptr [TheCamera->unk120 + 0x2c] + AND EDI,0x7f + CALL 140dad060:GetAVObjectFromHavok <76160> + + lVar1 = (**(code **)(*(longlong *)param_1 + 0x28))(param_1); + mov rax, rcx + ret + */ + + if (!best.hit) return result; + typedef NiAVObject*(__fastcall* _GetUserData)(bhkShapeList*); + auto av = Offsets::Get<_GetUserData>(76160)(best.hit); + result.hit = av != nullptr; + + // What a useless function, only returning a valid character if it hits the actor origin? + /* + typedef Character*(__fastcall* ExtractCharacterFromTraceRes)(NiAVObject*); + if (result.hit) { + auto character = Offsets::Get(19323)(av); + if (character && *reinterpret_cast(reinterpret_cast(character) + 0x1a) == '>') { + result.hitCharacter = character; + } + } + */ + + return result; } \ No newline at end of file