Smooth remote proxy rotation and conditional snapping
Add interpolation for remote proxy rotation and conditional snapping logic. Introduces kProxyRotationLerpAlpha and a ShouldSnapRemoteMovement declaration, and uses LerpRotation to smoothly apply headings for non-slot proxies (and slot-backed proxies in frame smoothing) unless movement type or large drift forces a snap. Adds a kLargeDriftSnapDistance threshold and threads a computed headingToApply through proxy locomotion/animation calls. Also updates the informational log to mention rotation lerp and removes an unused void cast.
This commit is contained in:
@@ -84,6 +84,7 @@ namespace
|
|||||||
constexpr auto kDebugRuntimeProxyOffsetZ = 0.0F;
|
constexpr auto kDebugRuntimeProxyOffsetZ = 0.0F;
|
||||||
constexpr auto kDebugRuntimeProxyScale = 1.50F;
|
constexpr auto kDebugRuntimeProxyScale = 1.50F;
|
||||||
constexpr float kProxyPositionLerpAlpha = 0.15F;
|
constexpr float kProxyPositionLerpAlpha = 0.15F;
|
||||||
|
constexpr float kProxyRotationLerpAlpha = 0.25F;
|
||||||
constexpr std::size_t kMaxRuntimeProxyActors = 4;
|
constexpr std::size_t kMaxRuntimeProxyActors = 4;
|
||||||
constexpr float kDisconnectedProxyReuseDelaySeconds = 30.0F;
|
constexpr float kDisconnectedProxyReuseDelaySeconds = 30.0F;
|
||||||
// Keep inactive proxies inside the loaded test cell instead of disabling actor
|
// Keep inactive proxies inside the loaded test cell instead of disabling actor
|
||||||
@@ -514,6 +515,8 @@ namespace
|
|||||||
// proxy's active AIProcess and causes forward/back glitching. Proxy locomotion
|
// proxy's active AIProcess and causes forward/back glitching. Proxy locomotion
|
||||||
// animation is driven by graph variables (Speed/speedSampled) instead of real velocity.
|
// animation is driven by graph variables (Speed/speedSampled) instead of real velocity.
|
||||||
|
|
||||||
|
bool ShouldSnapRemoteMovement(std::string_view a_movementType);
|
||||||
|
|
||||||
// Hybrid movement strategy for AI-driven animations
|
// Hybrid movement strategy for AI-driven animations
|
||||||
// Phase 1: Let the proxy's natural AI/character controller drive movement
|
// Phase 1: Let the proxy's natural AI/character controller drive movement
|
||||||
// This allows Move() to be called, which updates velocity and plays animations
|
// This allows Move() to be called, which updates velocity and plays animations
|
||||||
@@ -563,8 +566,6 @@ namespace
|
|||||||
bool a_shouldSnap,
|
bool a_shouldSnap,
|
||||||
ProxyActorSlot* a_optSlot = nullptr)
|
ProxyActorSlot* a_optSlot = nullptr)
|
||||||
{
|
{
|
||||||
(void)a_remotePlayer;
|
|
||||||
|
|
||||||
// DEBUG: Log AI state once per proxy
|
// DEBUG: Log AI state once per proxy
|
||||||
if (g_loggedProxies.find(&a_proxy) == g_loggedProxies.end()) {
|
if (g_loggedProxies.find(&a_proxy) == g_loggedProxies.end()) {
|
||||||
g_loggedProxies.insert(&a_proxy);
|
g_loggedProxies.insert(&a_proxy);
|
||||||
@@ -605,8 +606,27 @@ namespace
|
|||||||
a_optSlot->interpolationComponent.Clear();
|
a_optSlot->interpolationComponent.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always apply authoritative heading directly.
|
// Slot-backed proxies get per-frame rotation smoothing in ApplySmoothFrameMovement.
|
||||||
a_proxy.SetHeading(a_heading);
|
if (!a_optSlot) {
|
||||||
|
const bool shouldSnapHeading =
|
||||||
|
ShouldSnapRemoteMovement(a_remotePlayer.movementType) || distanceDrift >= kLargeDriftSnapDistance;
|
||||||
|
float headingToApply = a_heading;
|
||||||
|
if (!shouldSnapHeading) {
|
||||||
|
const float rotationAlpha = a_shouldSnap ?
|
||||||
|
kProxyRotationLerpAlpha :
|
||||||
|
std::clamp(
|
||||||
|
deltaSeconds * kInterpolationRatePerSecond,
|
||||||
|
kMinInterpolationAlpha,
|
||||||
|
kMaxInterpolationAlpha);
|
||||||
|
headingToApply = F4T::InterpolationSystem::LerpRotation(
|
||||||
|
a_proxy.GetHeading(),
|
||||||
|
a_heading,
|
||||||
|
rotationAlpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
a_proxy.SetHeading(headingToApply);
|
||||||
|
}
|
||||||
|
|
||||||
a_proxy.SetPosition(positionToApply, true);
|
a_proxy.SetPosition(positionToApply, true);
|
||||||
|
|
||||||
// Character controller velocity is injected once per frame in ApplySmoothFrameMovement
|
// Character controller velocity is injected once per frame in ApplySmoothFrameMovement
|
||||||
@@ -4307,8 +4327,9 @@ bool IsConsolePlaceAtMeCandidateActor(const RE::Actor& a_actor, const RE::Player
|
|||||||
}
|
}
|
||||||
if (!g_proxySmoothingEnabledLogged) {
|
if (!g_proxySmoothingEnabledLogged) {
|
||||||
LogInfoWithLocalPlayerPrefix(std::format(
|
LogInfoWithLocalPlayerPrefix(std::format(
|
||||||
"Proxy actor smoothing enabled for normal movement with position lerp alpha {:.2f}. Rotation still snaps; TODO: add wrapped angle smoothing later.",
|
"Proxy actor smoothing enabled for normal movement with position lerp alpha {:.2f} and wrapped rotation lerp alpha {:.2f}.",
|
||||||
kProxyPositionLerpAlpha));
|
kProxyPositionLerpAlpha,
|
||||||
|
kProxyRotationLerpAlpha));
|
||||||
g_proxySmoothingEnabledLogged = true;
|
g_proxySmoothingEnabledLogged = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5391,8 +5412,19 @@ bool IsConsolePlaceAtMeCandidateActor(const RE::Actor& a_actor, const RE::Player
|
|||||||
RE::NiPoint3 currentPosition = proxy->GetPosition();
|
RE::NiPoint3 currentPosition = proxy->GetPosition();
|
||||||
RE::NiPoint3 deltaToTarget = slot.targetPosition - currentPosition;
|
RE::NiPoint3 deltaToTarget = slot.targetPosition - currentPosition;
|
||||||
float distanceToTarget = deltaToTarget.Length();
|
float distanceToTarget = deltaToTarget.Length();
|
||||||
|
constexpr float kLargeDriftSnapDistance = 1500.0F;
|
||||||
|
|
||||||
proxy->SetHeading(slot.targetHeading);
|
const bool shouldSnapHeading =
|
||||||
|
slot.lastRemotePlayerSnapshot &&
|
||||||
|
ShouldSnapRemoteMovement(slot.lastRemotePlayerSnapshot->movementType);
|
||||||
|
float headingToApply = slot.targetHeading;
|
||||||
|
if (!shouldSnapHeading && distanceToTarget < kLargeDriftSnapDistance) {
|
||||||
|
headingToApply = F4T::InterpolationSystem::LerpRotation(
|
||||||
|
proxy->GetHeading(),
|
||||||
|
slot.targetHeading,
|
||||||
|
kProxyRotationLerpAlpha);
|
||||||
|
}
|
||||||
|
proxy->SetHeading(headingToApply);
|
||||||
|
|
||||||
const auto frameNow = std::chrono::steady_clock::now();
|
const auto frameNow = std::chrono::steady_clock::now();
|
||||||
float frameDeltaSeconds = 1.0F / 60.0F;
|
float frameDeltaSeconds = 1.0F / 60.0F;
|
||||||
@@ -5416,18 +5448,17 @@ bool IsConsolePlaceAtMeCandidateActor(const RE::Actor& a_actor, const RE::Player
|
|||||||
F4T::ProxyPuppet::SetProxyLocomotionState(
|
F4T::ProxyPuppet::SetProxyLocomotionState(
|
||||||
proxy->GetFormID(),
|
proxy->GetFormID(),
|
||||||
-1.0F,
|
-1.0F,
|
||||||
slot.puppetDirection);
|
headingToApply);
|
||||||
F4T::ProxyPuppet::ApplyProxyLocomotionFrame(
|
F4T::ProxyPuppet::ApplyProxyLocomotionFrame(
|
||||||
*proxy,
|
*proxy,
|
||||||
RE::NiPoint3{},
|
RE::NiPoint3{},
|
||||||
frameDeltaSeconds,
|
frameDeltaSeconds,
|
||||||
-1.0F,
|
-1.0F,
|
||||||
slot.puppetDirection);
|
headingToApply);
|
||||||
SyncProxyAnimationFromMotion(slot, *proxy, 0.0F);
|
SyncProxyAnimationFromMotion(slot, *proxy, 0.0F);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr float kLargeDriftSnapDistance = 1500.0F;
|
|
||||||
RE::NiPoint3 nextPosition;
|
RE::NiPoint3 nextPosition;
|
||||||
if (distanceToTarget > kLargeDriftSnapDistance) {
|
if (distanceToTarget > kLargeDriftSnapDistance) {
|
||||||
nextPosition = slot.targetPosition;
|
nextPosition = slot.targetPosition;
|
||||||
@@ -5452,7 +5483,7 @@ bool IsConsolePlaceAtMeCandidateActor(const RE::Actor& a_actor, const RE::Player
|
|||||||
frameDelta,
|
frameDelta,
|
||||||
frameDeltaSeconds,
|
frameDeltaSeconds,
|
||||||
slot.puppetGraphSpeed,
|
slot.puppetGraphSpeed,
|
||||||
slot.puppetDirection);
|
headingToApply);
|
||||||
|
|
||||||
const float horizontalDelta =
|
const float horizontalDelta =
|
||||||
std::sqrt((frameDelta.x * frameDelta.x) + (frameDelta.y * frameDelta.y));
|
std::sqrt((frameDelta.x * frameDelta.x) + (frameDelta.y * frameDelta.y));
|
||||||
|
|||||||
Reference in New Issue
Block a user