/** 
 * @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.
//

class LLViewerObject;

//  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;

public:
    LowPassFilter() :
    mInitialized(false),
    mFiltered()
    { }

    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;
    }
};

//  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);
    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 RegionCrossExtrapolate
{
private:
    std::unique_ptr<RegionCrossExtrapolateImpl> mImpl;

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
