FIRE-29021: Replace broken "Stop" region crossing behavior with improved version by animats

master
Ansariel 2020-04-12 00:28:16 +02:00
parent 44fe667661
commit 852eae68b3
6 changed files with 384 additions and 2 deletions

View File

@ -208,6 +208,7 @@ set(viewer_SOURCE_FILES
fsradarentry.cpp
fsradarlistctrl.cpp
fsradarmenu.cpp
fsregioncross.cpp
fsscriptlibrary.cpp
fsscrolllistctrl.cpp
fsslurlcommand.cpp
@ -966,6 +967,7 @@ set(viewer_HEADER_FILES
fsradarentry.h
fsradarlistctrl.h
fsradarmenu.h
fsregioncross.h
fsscriptlibrary.h
fsscrolllistctrl.h
fsslurl.h

View File

@ -25083,6 +25083,39 @@ Change of this parameter will affect the layout of buttons in notification toast
<key>Value</key>
<integer>0</integer>
</map>
<key>FSRegionCrossingPositionErrorLimit</key>
<map>
<key>Comment</key>
<string>Region crossing position error limit in meters</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>F32</string>
<key>Value</key>
<real>0.25</real>
</map>
<key>FSRegionCrossingAngleErrorLimit</key>
<map>
<key>Comment</key>
<string>Region crossing angle error limit in degrees</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>F32</string>
<key>Value</key>
<real>20.0</real>
</map>
<key>FSRegionCrossingSmoothingTime</key>
<map>
<key>Comment</key>
<string>Region crossing smoothing filter time in seconds</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>F32</string>
<key>Value</key>
<real>10.0</real>
</map>
<key>FSStatisticsNoFocus</key>
<map>
<key>Comment</key>

View File

@ -0,0 +1,186 @@
/**
* @file fsregioncross.cpp
* @brief Improvements to region crossing display
* @author nagle@animats.com
*
* $LicenseInfo:firstyear=2013&license=viewerlgpl$
* Phoenix Firestorm Viewer Source Code
* Copyright (C) 2020 Animats
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* The Phoenix Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA
* http://www.firestormviewer.org
*/
#include "llviewerprecompiledheaders.h" // regular Firestorm includes
#include "llviewercontrol.h"
#include "llmath.h" // normal build
#include "llviewerobject.h"
#include "llframetimer.h"
#include "fsregioncross.h"
//
// Improved region crossing time limit prediction.
//
// Applied when
// 1) object has an avatar sitting on it
// 2) object has been seen to move since sat on
// 3) object is outside the 0..256m region boundaries.
//
// In other words, a vehicle with an avatar doing a region crossing.
//
// This computes a safe time limit for extrapolating vehicle position and
// rotation during a region crossing, when the simulator to simulator handoff
// is in progress and no object updates are being received. Goal is to keep
// the position and rotation errors within set bounds.
//
// The smoother the movement of the vehicle approaching the sim crossing,
// the longer the allowed extrapolation will be.
//
//
// Constructor -- called when something sits on the object.
//
// Potential vehicle, but might be just a chair.
// We don't start the movement averaging until there's movement at least once.
//
RegionCrossExtrapolateImpl::RegionCrossExtrapolateImpl(const LLViewerObject& vo) : // constructor
mOwner(vo), // back ref to owner
mPreviousUpdateTime(0), // time of last update
mMoved(false) // has not moved yet
{
LL_INFOS() << "Object " << vo.getID().asString() << " has sitter." << LL_ENDL; // log sit event
}
//
// update -- called for each object update message to "vehicle" objects.
//
// This is called for any object with an avatar sitting on it.
// If the object never moves, it's not a vehicle and we can skip this.
// If it has moved, we treat it as a vehicle.
//
void RegionCrossExtrapolateImpl::update()
{
if (!mMoved) // if not seen to move
{ LLVector3 rawvel = mOwner.getVelocity(); // velocity in world coords
if (rawvel.mV[VX] != 0.0 || rawvel.mV[VY] != 0.0 || rawvel.mV[VZ] != 0) // check for nonzero velocity
{ mMoved = true; } // moved, has seated avatar, thus is vehicle
else
{ return; } // sitting on stationary object, skip this
}
// Moving seat - do the extrapolation calculations
F64 dt = 1.0/45.0; // dt used on first value - one physics frame on server
F64 now = LLFrameTimer::getElapsedSeconds(); // timestamp
if (mPreviousUpdateTime != 0.0)
{ dt = now - mPreviousUpdateTime; // change since last update
// Could adjust here for ping time and time dilation, but the filter isn't that
// sensitive to minor variations dt and it would just complicate things.
}
mPreviousUpdateTime = now;
LLQuaternion rot = mOwner.getRotationRegion(); // transform in global coords
const LLQuaternion& inverserot = rot.conjugate(); // transform global to local
LLVector3 vel = mOwner.getVelocity()*inverserot; // velocity in object coords
LLVector3 angvel = mOwner.getAngularVelocity()*inverserot; // angular velocity in object coords
mFilteredVel.update(vel,dt); // accum into filter in object coords
mFilteredAngVel.update(angvel,dt); // accum into filter in object coords
}
//
// dividesafe -- floating divide with divide by zero check
//
// Returns infinity for a divide by near zero.
//
static inline F32 dividesafe(F32 num, F32 denom)
{ return((denom > FP_MAG_THRESHOLD // avoid divide by zero
|| denom < -FP_MAG_THRESHOLD)
? (num / denom)
: std::numeric_limits<F32>::infinity()); // return infinity if zero divide
}
//
// getextraptimelimit -- don't extrapolate further ahead than this during a region crossing.
//
// Returns seconds of extrapolation that will probably stay within set limits of error.
//
F32 RegionCrossExtrapolateImpl::getextraptimelimit() const
{
// Debug tuning parameters. This code will try to limit the maximum position and angle error to the specified limits.
// The limits can be adjusted as debug symbols or in settings.xml, but that should not be necessary.
static LLCachedControl<F32> fsRegionCrossingPositionErrorLimit(gSavedSettings, "FSRegionCrossingPositionErrorLimit");
static LLCachedControl<F32> fsRegionCrossingAngleErrorLimit(gSavedSettings, "FSRegionCrossingAngleErrorLimit");
// Time limit is max allowed error / error. Returns worst case (smallest) of vel and angular vel limits.
LLQuaternion rot = mOwner.getRotationRegion(); // transform in global coords
const LLQuaternion& inverserot = rot.conjugate(); // transform global to local
// Calculate safe extrapolation time limit.
F32 extrapTimeLimit = std::min(
dividesafe(fsRegionCrossingPositionErrorLimit,
((mOwner.getVelocity()*inverserot - mFilteredVel.get()).length())),
dividesafe(fsRegionCrossingAngleErrorLimit,
((mOwner.getAngularVelocity()*inverserot - mFilteredAngVel.get()).length())));
LL_INFOS() << "Region cross extrapolation safe limit " << extrapTimeLimit << " secs." << LL_ENDL;
return(extrapTimeLimit); // do not extrapolate more than this
}
//
// ifsaton -- True if object is being sat upon.
//
// Potential vehicle.
//
BOOL RegionCrossExtrapolate::ifsaton(const LLViewerObject& vo) // true if root object and being sat on
{ if (!vo.isRoot()) { return(false); } // not root, cannot be sat upon
for (auto iter = vo.getChildren().begin(); // check for avatar as child of root
iter != vo.getChildren().end(); iter++)
{
LLViewerObject* child = *iter; // get child
if (child->isAvatar()) // avatar as child
{
return(true); // we have a sitter
}
}
return(false); // no avatar children, not sat on
}
//
// LowPassFilter -- the low pass filter for smoothing velocities.
//
// Works on vectors.
//
// Filter function is scaled by 1- 1/((1+(1/filterval))^secs)
// This is so samples which represent more time are weighted more.
//
// The filterval function is a curve which goes through (0,0) and gradually
// approaches 1 for larger values of secs. Secs is the time since the last update.
// We assume that the time covered by an update had reasonably uniform velocity
// and angular velocity, since if those were changing rapidly, the server would send
// more object updates.
//
// Setting filter smoothing time to zero turns off the filter and results in the predictor
// always returning a very large value and not affecting region crossing times.
// So that's how to turn this off, if desired.
//
void LowPassFilter::update(const LLVector3& val, F32 secs) // add new value into filter
{
static LLCachedControl<F32> fsRegionCrossingSmoothingTime(gSavedSettings, "FSRegionCrossingSmoothingTime");
if (!mInitialized) // if not initialized yet
{ mFiltered = val; // just use new value
mInitialized = true;
return;
}
F32 filtermult = 1.0; // no filtering if zero filter time
if (fsRegionCrossingSmoothingTime > 0.001) // avoid divide by zero
{ filtermult = 1.0 - 1.0/pow(1.0+1.0/fsRegionCrossingSmoothingTime,secs); } // filter scale factor
mFiltered = val * filtermult + mFiltered*(1.0-filtermult); // low pass filter
}

View File

@ -0,0 +1,136 @@
/**
* @file fsregioncross.h
* @brief Improvements to region crossing display
* @author nagle@animats.com
*
* $LicenseInfo:firstyear=2013&license=viewerlgpl$
* Phoenix Firestorm Viewer Source Code
* Copyright (C) 2020 Animats
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* The Phoenix Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA
* http://www.firestormviewer.org
*/
#ifndef FS_FSREGIONCROSS_H
#define FS_FSREGIONCROSS_H
//
// Improved region crossing support.
//
#include <memory>
#include <limits>
class LLViewerObject; // forward declaration
//
// LowPassFilter -- a simple Kalman low-pass filter.
//
// Supports nonuniform time deltas between samples, since object update times are not consistent.
//
class LowPassFilter
{
private:
LLVector3 mFiltered; // filtered value
BOOL mInitialized; // true if initialized
public:
LowPassFilter() : // constructor
mInitialized(false),
mFiltered(0.0,0.0,0.0)
{}
void update(const LLVector3& val, F32 dt); // add new value into filter
const LLVector3& get() const // get filtered output
{
return(mFiltered); // already stored
}
void clear()
{
mInitialized = false; // not initialized yet
}
};
//
// RegionCrossExtrapolateImpl -- the extrapolation limit calculator.
//
// One of these is created when an object is sat upon. If the
// seat moves, it's effectively a vehicle, so we start calculating
// region crossing safe extrapolation times. If the seat never moves,
// we still allocate one of these, but it doesn't do anything.
// When the avatar stands, this object is released.
// If the LLViewerObject is deleted, so is this object.
//
class RegionCrossExtrapolateImpl // Implementation of region cross extrapolation control
{
private:
const LLViewerObject& mOwner; // ref to owning object
F64 mPreviousUpdateTime; // previous update time
LowPassFilter mFilteredVel; // filtered velocity
LowPassFilter mFilteredAngVel; // filtered angular velocity
BOOL mMoved; // seen to move at least once
public:
RegionCrossExtrapolateImpl(const LLViewerObject& vo); // constructor
void update(); // update on object update message
F32 getextraptimelimit() const; // don't extrapolate more than this
BOOL hasmoved() const { return(mMoved); } // true if has been seen to move with sitter
};
//
// RegionCrossExtrapolate -- calculate safe limit on how long to extrapolate after a region crossing
//
// Member object of llViewerObject. For vehicles, a RegionCrossExtrapolateImpl is allocated to do the real work.
// Call "update" for each new object update.
// Call "changedlink" for any object update which changes parenting.
// Get the extrapolation limit time with getextraptimelimit.
//
class LLViewerObject; // forward
class RegionCrossExtrapolate {
private:
std::unique_ptr<RegionCrossExtrapolateImpl> mImpl; // pointer to region cross extrapolator, if present
protected:
BOOL ifsaton(const LLViewerObject& vo); // true if root object and being sat on
public:
void update(const LLViewerObject& vo) // new object update message received
{ if (mImpl.get()) { mImpl->update(); } // update extrapolator if present
}
void changedlink(const LLViewerObject& vo) // parent or child changed, check if extrapolation object needed
{
if (ifsaton(vo)) // if this object is now the root of a linkset with an avatar
{ if (!mImpl.get()) // if no extrapolation implementor
{ mImpl.reset(new RegionCrossExtrapolateImpl(vo)); } // add an extrapolator
} else { // not a vehicle
if (mImpl.get())
{ mImpl.reset(); } // no longer needed
}
}
BOOL ismovingssaton(const LLViewerObject &vo)
{ if (!mImpl.get()) { return(false); } // not sat on
return(mImpl->hasmoved()); // sat on, check for moving
}
F32 getextraptimelimit() const // get extrapolation time limit
{ if (mImpl.get()) { return(mImpl->getextraptimelimit()); } // get extrapolation time limit if vehicle
return(std::numeric_limits<F32>::infinity()); // no limit if not a vehicle
}
};
#endif // FS_REGIONCROSS_H

View File

@ -924,6 +924,8 @@ void LLViewerObject::addChild(LLViewerObject *childp)
mChildList.push_back(childp);
childp->afterReparent();
}
mExtrap.changedlink(*this); // <FS:JN> if linking update, check for sitters
}
void LLViewerObject::onReparent(LLViewerObject *old_parent, LLViewerObject *new_parent)
@ -955,6 +957,8 @@ void LLViewerObject::removeChild(LLViewerObject *childp)
}
}
mExtrap.changedlink(*this); // <FS:JN> if linking update, check for sitters
if (childp->isSelected())
{
LLSelectMgr::getInstance()->deselectObjectAndFamily(childp);
@ -2420,6 +2424,10 @@ U32 LLViewerObject::processUpdateMessage(LLMessageSystem *mesgsys,
avatar->clampAttachmentPositions();
}
// <FS:JN> Region crossing extrapolation improvement
mExtrap.update(*this); // update extrapolation if needed
mRegionCrossExpire = 0; // restart extrapolation clock on object update
// If we're snapping the position by more than 0.5m, update LLViewerStats::mAgentPositionSnaps
if ( asAvatar() && asAvatar()->isSelf() && (mag_sqr > 0.25f) )
{
@ -2752,7 +2760,13 @@ void LLViewerObject::interpolateLinearMotion(const F64SecondsImplicit& frame_tim
// so just write down time 'after the fact', it is far from optimal in
// case of lags, but for lags sMaxUpdateInterpolationTime will kick in first
LL_DEBUGS("Interpolate") << "Predicted region crossing, new position " << new_pos << LL_ENDL;
mRegionCrossExpire = frame_time + sMaxRegionCrossingInterpolationTime;
// <FS:JN> Limit region crossing time using smart limiting
//mRegionCrossExpire = frame_time + sMaxRegionCrossingInterpolationTime;
F64Seconds saferegioncrosstimelimit(mExtrap.getextraptimelimit()); // longest time we can safely extrapolate
F64Seconds maxregioncrosstime = std::min(saferegioncrosstimelimit,sMaxRegionCrossingInterpolationTime); // new interpolation code
mRegionCrossExpire = frame_time + maxregioncrosstime; // region cross expires then
setAcceleration(LLVector3::zero); // no accel during region crossings
// <FS:JN> Limit region crossing time using smart limiting end
}
else if (frame_time > mRegionCrossExpire)
{
@ -2760,7 +2774,14 @@ void LLViewerObject::interpolateLinearMotion(const F64SecondsImplicit& frame_tim
// Stop motion
LL_DEBUGS("Interpolate") << "Predicting region crossing for too long, stopping at " << new_pos << LL_ENDL;
new_v.clear();
setAcceleration(LLVector3::zero);
// <FS:JN> For region crossing vehicles, stop rotation too. Paranoia consideration above about endlessly rotating objects does not apply.
//setAcceleration(LLVector3::zero);
if (mExtrap.ismovingssaton(*this)) // if moving and sat on and crossing regions, almost certainly a vehicle with avatars
{
setAngularVelocity(LLVector3::zero); // for region crossing vehicles, stop rotation too.
setAcceleration(LLVector3::zero); // Stop everything
}
// <FS:JN> Limit region crossing time using smart limiting end
mRegionCrossExpire = 0;
}
// <FS:Ansariel> FIRE-24184: Replace previous region crossing movement fix with LL's version and add option to turn it off

View File

@ -44,6 +44,8 @@
#include "llbbox.h"
#include "llrigginginfo.h"
#include "fsregioncross.h" // <FS:JN> Improved region crossing support
class LLAgent; // TODO: Get rid of this.
class LLAudioSource;
class LLAudioSourceVO;
@ -922,6 +924,8 @@ private:
EObjectUpdateType mLastUpdateType;
BOOL mLastUpdateCached;
RegionCrossExtrapolate mExtrap; // <FS:JN> improved extrapolator
// <FS:Techwolf Lupindo> export
public:
LLViewerPartSourceScript* getPartSourceScript() { return mPartSourcep.get(); }