diff --git a/features/Grass Collision/Shaders/Features/GrassCollision.ini b/features/Grass Collision/Shaders/Features/GrassCollision.ini index 312d7ff985..7c4d5a2a34 100644 --- a/features/Grass Collision/Shaders/Features/GrassCollision.ini +++ b/features/Grass Collision/Shaders/Features/GrassCollision.ini @@ -1,2 +1,2 @@ [Info] -Version = 1-1-0 \ No newline at end of file +Version = 1-2-0 \ No newline at end of file diff --git a/features/Grass Collision/Shaders/GrassCollision/GrassCollision.hlsli b/features/Grass Collision/Shaders/GrassCollision/GrassCollision.hlsli index 8d0e9f44da..648491a0ce 100644 --- a/features/Grass Collision/Shaders/GrassCollision/GrassCollision.hlsli +++ b/features/Grass Collision/Shaders/GrassCollision/GrassCollision.hlsli @@ -6,6 +6,7 @@ cbuffer GrassCollisionPerFrame : register(b5) bool EnableGrassCollision; float RadiusMultiplier; float DisplacementMultiplier; + float maxDistance; } struct StructuredCollision @@ -21,10 +22,10 @@ float3 GetDisplacedPosition(float3 position, float alpha, uint eyeIndex = 0) float3 worldPosition = mul(World[eyeIndex], float4(position, 1)).xyz; float3 displacement = 0; - // Player bound culling + // Player bound culling and distance from player culling { float dist = distance(boundCentre[eyeIndex].xyz, worldPosition); - if (dist > boundRadius) { + if ((dist > boundRadius) && (dist > maxDistance)) { return 0; } } diff --git a/src/Features/GrassCollision.cpp b/src/Features/GrassCollision.cpp index 9dab2c2eeb..7bdada71a9 100644 --- a/src/Features/GrassCollision.cpp +++ b/src/Features/GrassCollision.cpp @@ -31,7 +31,16 @@ void GrassCollision::DrawSettings() if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); - ImGui::Text("Distance from collision centres to apply collision"); + ImGui::Text("Distance from collision centres to apply collision."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + + ImGui::SliderFloat("Max Distance from Player", &settings.maxDistance, 0.0f, 1500.0f); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("Distance from player to apply collision (NPCs). 0 to disable NPC collisions."); ImGui::PopTextWrapPos(); ImGui::EndTooltip(); } @@ -45,6 +54,23 @@ void GrassCollision::DrawSettings() ImGui::EndTooltip(); } + if (ImGui::SliderInt("Calculation Frame Interval", (int*)&settings.frameInterval, 0, 30)) { + if (settings.frameInterval) // increment so mod math works (e.g., skip 1 frame means frame % 2). + settings.frameInterval++; + } + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::Text("How many frames to skip before calculating positions again. 0 means calculate every frame (most smooth/costly)."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + + ImGui::TreePop(); + } + if (ImGui::TreeNodeEx("Statistics", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::Text(std::format("Active/Total Actors : {}/{}", activeActorCount, totalActorCount).c_str()); + ImGui::Text(std::format("Total Collisions : {}", currentCollisionCount).c_str()); ImGui::TreePop(); } } @@ -131,38 +157,67 @@ void GrassCollision::UpdateCollisions() { auto state = RE::BSGraphics::RendererShadowState::GetSingleton(); - std::uint32_t currentCollisionCount = 0; - - std::vector collisionsData{}; - - if (auto player = RE::PlayerCharacter::GetSingleton()) { - if (auto root = player->Get3D(false)) { - auto position = player->GetPosition(); - RE::BSVisit::TraverseScenegraphCollision(root, [&](RE::bhkNiCollisionObject* a_object) -> RE::BSVisit::BSVisitControl { - RE::NiPoint3 centerPos; - float radius; - if (GetShapeBound(a_object, centerPos, radius)) { - radius *= settings.RadiusMultiplier; - CollisionSData data{}; - RE::NiPoint3 eyePosition{}; - for (int eyeIndex = 0; eyeIndex < eyeCount; eyeIndex++) { - if (!REL::Module::IsVR()) { - eyePosition = state->GetRuntimeData().posAdjust.getEye(); - } else - eyePosition = state->GetVRRuntimeData().posAdjust.getEye(eyeIndex); - data.centre[eyeIndex].x = centerPos.x - eyePosition.x; - data.centre[eyeIndex].y = centerPos.y - eyePosition.y; - data.centre[eyeIndex].z = centerPos.z - eyePosition.z; + auto frameCount = RE::BSGraphics::State::GetSingleton()->uiFrameCount; + + if (settings.frameInterval == 0 || frameCount % settings.frameInterval == 0) { // only calculate actor positions on some frames + currentCollisionCount = 0; + totalActorCount = 0; + activeActorCount = 0; + actorList.clear(); + collisionsData.clear(); + // actor query code from po3 under MIT + // https://github.com/powerof3/PapyrusExtenderSSE/blob/7a73b47bc87331bec4e16f5f42f2dbc98b66c3a7/include/Papyrus/Functions/Faction.h#L24C7-L46 + if (const auto processLists = RE::ProcessLists::GetSingleton(); processLists && settings.maxDistance > 0.0f) { + std::vector*> actors; + actors.push_back(&processLists->highActorHandles); // high actors are in combat or doing something interesting + for (auto array : actors) { + for (auto& actorHandle : *array) { + auto actorPtr = actorHandle.get(); + if (actorPtr && actorPtr.get() && actorPtr.get()->Is3DLoaded()) { + actorList.push_back(actorPtr.get()); + totalActorCount++; } - data.radius = radius; - currentCollisionCount++; - collisionsData.push_back(data); } - return RE::BSVisit::BSVisitControl::kContinue; - }); + } } - } + RE::NiPoint3 playerPosition; + if (auto player = RE::PlayerCharacter::GetSingleton()) { + actorList.push_back(player); + playerPosition = player->GetPosition(); + } + + for (const auto actor : actorList) { + if (auto root = actor->Get3D(false)) { + if (playerPosition.GetDistance(actor->GetPosition()) > settings.maxDistance) { // npc too far so skip + continue; + } + activeActorCount++; + RE::BSVisit::TraverseScenegraphCollision(root, [&](RE::bhkNiCollisionObject* a_object) -> RE::BSVisit::BSVisitControl { + RE::NiPoint3 centerPos; + float radius; + if (GetShapeBound(a_object, centerPos, radius)) { + radius *= settings.RadiusMultiplier; + CollisionSData data{}; + RE::NiPoint3 eyePosition{}; + for (int eyeIndex = 0; eyeIndex < eyeCount; eyeIndex++) { + if (!REL::Module::IsVR()) { + eyePosition = state->GetRuntimeData().posAdjust.getEye(); + } else + eyePosition = state->GetVRRuntimeData().posAdjust.getEye(eyeIndex); + data.centre[eyeIndex].x = centerPos.x - eyePosition.x; + data.centre[eyeIndex].y = centerPos.y - eyePosition.y; + data.centre[eyeIndex].z = centerPos.z - eyePosition.z; + } + data.radius = radius; + currentCollisionCount++; + collisionsData.push_back(data); + } + return RE::BSVisit::BSVisitControl::kContinue; + }); + } + } + } if (!currentCollisionCount) { CollisionSData data{}; ZeroMemory(&data, sizeof(data)); @@ -170,7 +225,6 @@ void GrassCollision::UpdateCollisions() currentCollisionCount = 1; } - static std::uint32_t colllisionCount = 0; bool collisionCountChanged = currentCollisionCount != colllisionCount; if (!collisions || collisionCountChanged) { diff --git a/src/Features/GrassCollision.h b/src/Features/GrassCollision.h index 6b1a92d12f..ffc955e6ea 100644 --- a/src/Features/GrassCollision.h +++ b/src/Features/GrassCollision.h @@ -19,6 +19,8 @@ struct GrassCollision : Feature std::uint32_t EnableGrassCollision = 1; float RadiusMultiplier = 2; float DisplacementMultiplier = 16; + float maxDistance = 1000.0; + std::uint32_t frameInterval = 0; }; struct alignas(16) PerFrame @@ -26,6 +28,7 @@ struct GrassCollision : Feature Vector4 boundCentre[2]; float boundRadius; Settings Settings; + float pad01[2]; }; struct CollisionSData @@ -35,6 +38,12 @@ struct GrassCollision : Feature }; std::unique_ptr collisions = nullptr; + std::uint32_t totalActorCount = 0; + std::uint32_t activeActorCount = 0; + std::uint32_t currentCollisionCount = 0; + std::vector actorList{}; + std::vector collisionsData{}; + std::uint32_t colllisionCount = 0; Settings settings;