/**
* @file fsposestate.h
* @brief a means to save and restore the instantaneous state of animations posing an avatar.
*
* $LicenseInfo:firstyear=2025&license=viewerlgpl$
* Phoenix Firestorm Viewer Source Code
* Copyright (c) 2025 Angeldark Raymaker @ Second Life
*
* 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
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#ifndef LL_FSPoseState_H
#define LL_FSPoseState_H
#include "llvoavatar.h"
#include "fsposingmotion.h"
class FSPoseState
{
public:
FSPoseState() = default;
virtual ~FSPoseState() = default;
public:
///
/// Captures the current animations posing the supplied avatar and how long they have been playing.
///
/// The avatar whose animations are to be captured.
void captureMotionStates(LLVOAvatar* avatar);
///
/// Updates the stored list of animations posing the avatar.
///
/// The avatar whose animations are to be captured.
/// The posing motion.
/// The names of the joints being recaptured.
void updateMotionStates(LLVOAvatar* avatar, FSPosingMotion* posingMotion, const std::vector& jointNamesRecaptured);
///
/// Removes all current animation states for the supplied avatar.
///
/// The avatar whose animations are to be purged.
void purgeMotionStates(LLVOAvatar* avatar);
///
/// Writes any documented poses for the supplied avatar to the supplied stream.
///
/// The avatar whose animations may have been captured.
/// Whether to ignore ownership. For use when preparing saveRecord to send to another by collab.
/// The record to add to.
void writeMotionStates(LLVOAvatar* avatar, bool ignoreOwnership, LLSD* saveRecord);
///
/// Restores pose state(s) from the supplied record.
///
/// The avatar whose animations may have been captured.
/// Whether to ignore ownership. For use when reading a local file.
/// The record to read from.
void restoreMotionStates(LLVOAvatar* avatar, bool ignoreOwnership, LLSD pose);
///
/// Applies the motion states for the supplied avatar to the supplied motion.
///
/// The avatar to apply the motion state(s) to.
/// The posing motion to apply the state(s) to.
/// True if all the motion states for the supplied avatar have been applied, otherwise false.
///
/// In some ways this is like an AO: loading LLKeyframeMotions.
/// Once loaded, the LLKeyframeMotion is put at time fsMotionState.lastUpdateTime.
/// The joint-rotations for that LLKeyframeMotion are then restored to the base.
/// This examines sMotionStates for any avatarId matches; such as after a restoreMotionStates(...).
/// This could result in loading assets, thus a particular member of sMotionStates may take several attempts to load.
/// Motion(s) that the avatar does not have permissions for are not considered in the return boolean.
///
bool applyMotionStatesToPosingMotion(LLVOAvatar* avatar, FSPosingMotion* posingMotion);
private:
///
/// A class documenting the state of an animation for an avatar.
///
class fsMotionState
{
public:
///
/// The motion ID recorded animating the avatar ID.
///
LLAssetID motionId;
///
/// The play-time the motionId had progressed until the motion was captured.
///
F32 lastUpdateTime = 0.f;
///
/// Upon reloading, whether this record has been applied to the avatar.
///
bool motionApplied = false;
///
/// For non-gAgent, we permit a query of the inventory of a prim they are sitting on.
/// Because this involves latency, we may retry ownership checking at save-time.
///
bool requeriedAssetInventory = false;
///
/// Whether gAgent owns the pose, or the pose was loaded from XML.
///
bool gAgentOwnsPose = false;
///
/// Represents 'capture layers: how the user layers animations 'on top of' others.
///
S32 captureOrder = 0;
///
/// Represents in-layer order of capture.
///
S32 inLayerOrder = 0;
///
/// When reloading, and if not-empty, the bone-numbers this motionId should affect.
///
std ::vector jointNumbersAnimated;
};
///
/// Resets the priority for the named joints for the supplied posing motion at the supplied capture order.
///
/// The avatar being posed by the motion.
/// The posing motion.
/// The order of the capture.
void resetPriorityForCaptureOrder(LLVOAvatar* avatar, FSPosingMotion* posingMotion, S32 captureOrder);
///
/// Gets whether gAgentID owns, and thus can save information about the supplied motionId.
///
/// The avatar playing the supplied motionId.
/// The motionId of the animation.
/// True if the gAgent owns the motionId, otherwise false.
///
/// This only works reliably for self.
/// For motions playing on others, the motion needs to be an asset in gAgent's inventory.
///
bool canSaveMotionId(LLVOAvatar* avatarPlayingMotionId, LLAssetID motionId);
///
/// Examines gAgent's animation source list for the supplied animation Id.
///
/// The ID of the motion to query.
/// True if gAgent is playing the animation, otherwise false.
bool motionIdIsAgentAnimationSource(LLAssetID motionId);
///
/// Queries a specific condition of the supplied animation ID.
///
/// The avatar to query for.
/// The motion ID to query for.
///
/// True if the supplied avatar is sitting on an object owned by gAgent, and that object
/// contains an animation asset with the same assetId.
///
///
/// This is intended to test for a situation a photographer might arrange.
/// If you are sitting on photographer's prim, playing photographer's pose, and photographer wants to save their work,
/// this allows them to save the Animation ID and state to XML.
/// It is intended this be called twice at least, as it does not implement a callback onInventoryLoaded.
/// Presently this works fine: first time being when posing starts, second when pose is saved.
///
bool motionIdIsFromPrimAgentOwnsAgentIsSittingOn(LLVOAvatar* avatarPlayingMotionId, LLAssetID motionId);
///
/// Tests if all the members of supplied vector2 are members of supplied vector1.
///
/// The super-set.
/// The possible sub-set.
/// True if all members of vector2 are members of vector1, otherwise false.
bool vector2IsSubsetOfVector1(std::vector vector1, std::vector vector2);
///
/// Two symmetric methods for (de)serializing vectors to both XML and collab-safe short-as-possible strings and back again.
///
///
/// Collab-safe means ASCII-printable chars, and delimiter usage does not conflict with Collab's delimiter.
///
std::string encodeVectorToString(const std::vector& vector);
std::vector decodeStringToVector(std::string_view vector);
struct compareByCaptureOrder
{
bool operator()(const fsMotionState& a, const fsMotionState& b)
{
if (a.captureOrder < b.captureOrder)
return true; // Ascending order
if (a.captureOrder == b.captureOrder && a.inLayerOrder < b.inLayerOrder)
return true; // Ascending order in layer
return false;
}
};
static std::map> sMotionStates;
static std::map sCaptureOrder;
static std::map sMotionStatesOwnedByMe;
};
#endif // LL_FSPoseState_H