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 kDebugRuntimeProxyScale = 1.50F;
|
||||
constexpr float kProxyPositionLerpAlpha = 0.15F;
|
||||
constexpr float kProxyRotationLerpAlpha = 0.25F;
|
||||
constexpr std::size_t kMaxRuntimeProxyActors = 4;
|
||||
constexpr float kDisconnectedProxyReuseDelaySeconds = 30.0F;
|
||||
// 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
|
||||
// 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
|
||||
// Phase 1: Let the proxy's natural AI/character controller drive movement
|
||||
// This allows Move() to be called, which updates velocity and plays animations
|
||||
@@ -563,8 +566,6 @@ namespace
|
||||
bool a_shouldSnap,
|
||||
ProxyActorSlot* a_optSlot = nullptr)
|
||||
{
|
||||
(void)a_remotePlayer;
|
||||
|
||||
// DEBUG: Log AI state once per proxy
|
||||
if (g_loggedProxies.find(&a_proxy) == g_loggedProxies.end()) {
|
||||
g_loggedProxies.insert(&a_proxy);
|
||||
@@ -605,8 +606,27 @@ namespace
|
||||
a_optSlot->interpolationComponent.Clear();
|
||||
}
|
||||
|
||||
// Always apply authoritative heading directly.
|
||||
a_proxy.SetHeading(a_heading);
|
||||
// Slot-backed proxies get per-frame rotation smoothing in ApplySmoothFrameMovement.
|
||||
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);
|
||||
|
||||
// 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) {
|
||||
LogInfoWithLocalPlayerPrefix(std::format(
|
||||
"Proxy actor smoothing enabled for normal movement with position lerp alpha {:.2f}. Rotation still snaps; TODO: add wrapped angle smoothing later.",
|
||||
kProxyPositionLerpAlpha));
|
||||
"Proxy actor smoothing enabled for normal movement with position lerp alpha {:.2f} and wrapped rotation lerp alpha {:.2f}.",
|
||||
kProxyPositionLerpAlpha,
|
||||
kProxyRotationLerpAlpha));
|
||||
g_proxySmoothingEnabledLogged = true;
|
||||
}
|
||||
}
|
||||
@@ -5391,8 +5412,19 @@ bool IsConsolePlaceAtMeCandidateActor(const RE::Actor& a_actor, const RE::Player
|
||||
RE::NiPoint3 currentPosition = proxy->GetPosition();
|
||||
RE::NiPoint3 deltaToTarget = slot.targetPosition - currentPosition;
|
||||
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();
|
||||
float frameDeltaSeconds = 1.0F / 60.0F;
|
||||
@@ -5416,18 +5448,17 @@ bool IsConsolePlaceAtMeCandidateActor(const RE::Actor& a_actor, const RE::Player
|
||||
F4T::ProxyPuppet::SetProxyLocomotionState(
|
||||
proxy->GetFormID(),
|
||||
-1.0F,
|
||||
slot.puppetDirection);
|
||||
headingToApply);
|
||||
F4T::ProxyPuppet::ApplyProxyLocomotionFrame(
|
||||
*proxy,
|
||||
RE::NiPoint3{},
|
||||
frameDeltaSeconds,
|
||||
-1.0F,
|
||||
slot.puppetDirection);
|
||||
headingToApply);
|
||||
SyncProxyAnimationFromMotion(slot, *proxy, 0.0F);
|
||||
continue;
|
||||
}
|
||||
|
||||
constexpr float kLargeDriftSnapDistance = 1500.0F;
|
||||
RE::NiPoint3 nextPosition;
|
||||
if (distanceToTarget > kLargeDriftSnapDistance) {
|
||||
nextPosition = slot.targetPosition;
|
||||
@@ -5452,7 +5483,7 @@ bool IsConsolePlaceAtMeCandidateActor(const RE::Actor& a_actor, const RE::Player
|
||||
frameDelta,
|
||||
frameDeltaSeconds,
|
||||
slot.puppetGraphSpeed,
|
||||
slot.puppetDirection);
|
||||
headingToApply);
|
||||
|
||||
const float horizontalDelta =
|
||||
std::sqrt((frameDelta.x * frameDelta.x) + (frameDelta.y * frameDelta.y));
|
||||
|
||||
Reference in New Issue
Block a user