FIRE-29021: Replace broken "Stop" region crossing behavior with improved version by animats
parent
44fe667661
commit
852eae68b3
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(); }
|
||||
|
|
|
|||
Loading…
Reference in New Issue