diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index d57b4517cf..e67cfcedc2 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -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 diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 330b241bd0..b3cb89d7b7 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -25083,6 +25083,39 @@ Change of this parameter will affect the layout of buttons in notification toast Value 0 + FSRegionCrossingPositionErrorLimit + + Comment + Region crossing position error limit in meters + Persist + 1 + Type + F32 + Value + 0.25 + + FSRegionCrossingAngleErrorLimit + + Comment + Region crossing angle error limit in degrees + Persist + 1 + Type + F32 + Value + 20.0 + + FSRegionCrossingSmoothingTime + + Comment + Region crossing smoothing filter time in seconds + Persist + 1 + Type + F32 + Value + 10.0 + FSStatisticsNoFocus Comment diff --git a/indra/newview/fsregioncross.cpp b/indra/newview/fsregioncross.cpp new file mode 100644 index 0000000000..e20ca443ce --- /dev/null +++ b/indra/newview/fsregioncross.cpp @@ -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::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 fsRegionCrossingPositionErrorLimit(gSavedSettings, "FSRegionCrossingPositionErrorLimit"); + static LLCachedControl 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 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 +} + + + + diff --git a/indra/newview/fsregioncross.h b/indra/newview/fsregioncross.h new file mode 100644 index 0000000000..0122d49fd4 --- /dev/null +++ b/indra/newview/fsregioncross.h @@ -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 +#include + +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 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::infinity()); // no limit if not a vehicle + } +}; + +#endif // FS_REGIONCROSS_H diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp index e3d3f23a20..35fd368772 100644 --- a/indra/newview/llviewerobject.cpp +++ b/indra/newview/llviewerobject.cpp @@ -924,6 +924,8 @@ void LLViewerObject::addChild(LLViewerObject *childp) mChildList.push_back(childp); childp->afterReparent(); } + + mExtrap.changedlink(*this); // 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); // if linking update, check for sitters + if (childp->isSelected()) { LLSelectMgr::getInstance()->deselectObjectAndFamily(childp); @@ -2420,6 +2424,10 @@ U32 LLViewerObject::processUpdateMessage(LLMessageSystem *mesgsys, avatar->clampAttachmentPositions(); } + // 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; + // 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 + // 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); + // 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 + } + // Limit region crossing time using smart limiting end mRegionCrossExpire = 0; } // FIRE-24184: Replace previous region crossing movement fix with LL's version and add option to turn it off diff --git a/indra/newview/llviewerobject.h b/indra/newview/llviewerobject.h index b23c13326d..320090b0d8 100644 --- a/indra/newview/llviewerobject.h +++ b/indra/newview/llviewerobject.h @@ -44,6 +44,8 @@ #include "llbbox.h" #include "llrigginginfo.h" +#include "fsregioncross.h" // 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; // improved extrapolator + // export public: LLViewerPartSourceScript* getPartSourceScript() { return mPartSourcep.get(); }