#include "fsposestate.h" #include "llinventorymodel.h" // gInventory std::map> FSPoseState::sMotionStates; std::map FSPoseState::sCaptureOrder; std::map FSPoseState::sMotionStatesOwnedByMe; void FSPoseState::captureMotionStates(LLVOAvatar* avatar) { if (!avatar) return; sCaptureOrder[avatar->getID()] = 0; int animNumber = 0; for (auto anim_it = avatar->mPlayingAnimations.begin(); anim_it != avatar->mPlayingAnimations.end(); ++anim_it) { LLKeyframeMotion* motion = dynamic_cast(avatar->findMotion(anim_it->first)); if (!motion) continue; fsMotionState newState; newState.motionId = anim_it->first; newState.lastUpdateTime = motion->getLastUpdateTime(); newState.captureOrder = 0; newState.inLayerOrder = animNumber++; newState.gAgentOwnsPose = canSaveMotionId(avatar, anim_it->first); sMotionStates[avatar->getID()].push_back(newState); } } void FSPoseState::updateMotionStates(LLVOAvatar* avatar, FSPosingMotion* posingMotion, const std::vector& jointNumbersRecaptured) { if (!avatar || !posingMotion) return; sCaptureOrder[avatar->getID()]++; S32 animNumber = 0; // if an animation for avatar is a subset of jointNumbersRecaptured, delete it // this happens on second/subsequent recaptures; the first recapture is no longer needed for (auto it = sMotionStates[avatar->getID()].begin(); it != sMotionStates[avatar->getID()].end();) { if (vector2IsSubsetOfVector1(jointNumbersRecaptured, (*it).jointNumbersAnimated)) it = sMotionStates[avatar->getID()].erase(it); else it++; } for (auto anim_it = avatar->mPlayingAnimations.begin(); anim_it != avatar->mPlayingAnimations.end(); anim_it++) { LLKeyframeMotion* motion = dynamic_cast(avatar->findMotion(anim_it->first)); if (!motion) continue; if (!posingMotion->otherMotionAnimatesJoints(motion, jointNumbersRecaptured)) continue; bool foundMatch = false; for (auto it = sMotionStates[avatar->getID()].begin(); it != sMotionStates[avatar->getID()].end(); it++) { bool motionIdMatches = (*it).motionId == anim_it->first; bool updateTimesMatch = (*it).lastUpdateTime == motion->getLastUpdateTime(); // consider when recapturing the same animation at different times for a subset of bones foundMatch = motionIdMatches && updateTimesMatch; if (foundMatch) break; } if (foundMatch) continue; fsMotionState newState; newState.motionId = anim_it->first; newState.lastUpdateTime = motion->getLastUpdateTime(); newState.jointNumbersAnimated = jointNumbersRecaptured; newState.captureOrder = sCaptureOrder[avatar->getID()]; newState.inLayerOrder = animNumber++; newState.gAgentOwnsPose = canSaveMotionId(avatar, anim_it->first); sMotionStates[avatar->getID()].push_back(newState); } } void FSPoseState::purgeMotionStates(LLVOAvatar* avatar) { if (!avatar) return; sMotionStates[avatar->getID()].clear(); } void FSPoseState::writeMotionStates(LLVOAvatar* avatar, bool ignoreOwnership, LLSD* saveRecord) { if (!avatar) return; S32 animNumber = 0; for (auto it = sMotionStates[avatar->getID()].begin(); it != sMotionStates[avatar->getID()].end(); ++it) { if (!ignoreOwnership && !it->gAgentOwnsPose) { if (it->requeriedAssetInventory) continue; it->gAgentOwnsPose = canSaveMotionId(avatar, it->motionId); it->requeriedAssetInventory = true; if (!it->gAgentOwnsPose) continue; } std::string uniqueAnimId = "poseState" + std::to_string(animNumber++); (*saveRecord)[uniqueAnimId]["animationId"] = it->motionId.asString(); (*saveRecord)[uniqueAnimId]["lastUpdateTime"] = it->lastUpdateTime; (*saveRecord)[uniqueAnimId]["jointNumbersAnimated"] = encodeVectorToString(it->jointNumbersAnimated); (*saveRecord)[uniqueAnimId]["captureOrder"] = it->captureOrder; (*saveRecord)[uniqueAnimId]["inLayerOrder"] = it->inLayerOrder; } } void FSPoseState::restoreMotionStates(LLVOAvatar* avatar, bool ignoreOwnership, LLSD pose) { if (!avatar) return; sCaptureOrder[avatar->getID()] = 0; for (auto itr = pose.beginMap(); itr != pose.endMap(); ++itr) { std::string const& name = itr->first; LLSD const& control_map = itr->second; if (!name.starts_with("poseState")) continue; fsMotionState newState; if (control_map.has("animationId")) { std::string const name = control_map["animationId"].asString(); LLUUID animId; if (LLUUID::parseUUID(name, &animId)) { newState.motionId = animId; newState.gAgentOwnsPose = ignoreOwnership || canSaveMotionId(avatar, animId); if (ignoreOwnership) sMotionStatesOwnedByMe[animId] = true; } } if (control_map.has("lastUpdateTime")) newState.lastUpdateTime = (F32)control_map["lastUpdateTime"].asReal(); if (control_map.has("jointNumbersAnimated")) newState.jointNumbersAnimated = decodeStringToVector(control_map["jointNumbersAnimated"].asString()); if (control_map.has("captureOrder")) newState.captureOrder = control_map["captureOrder"].asInteger(); if (control_map.has("inLayerOrder")) newState.inLayerOrder = control_map["inLayerOrder"].asInteger(); if (newState.captureOrder > sCaptureOrder[avatar->getID()]) sCaptureOrder[avatar->getID()] = newState.captureOrder; sMotionStates[avatar->getID()].push_back(newState); } } bool FSPoseState::applyMotionStatesToPosingMotion(LLVOAvatar* avatar, FSPosingMotion* posingMotion) { if (!avatar || !posingMotion) return false; bool allMotionsApplied = true; std::sort(sMotionStates[avatar->getID()].begin(), sMotionStates[avatar->getID()].end(), compareByCaptureOrder()); S32 lastCaptureOrder = 0; bool needPriorityReset = false; for (auto it = sMotionStates[avatar->getID()].begin(); it != sMotionStates[avatar->getID()].end(); it++) { needPriorityReset = it->captureOrder > lastCaptureOrder; if (it->motionApplied) continue; LLKeyframeMotion* kfm = dynamic_cast(avatar->findMotion(it->motionId)); if (kfm) { if (needPriorityReset) { lastCaptureOrder = it->captureOrder; resetPriorityForCaptureOrder(avatar, posingMotion, lastCaptureOrder); } it->motionApplied = posingMotion->loadOtherMotionToBaseOfThisMotion(kfm, it->lastUpdateTime, it->jointNumbersAnimated); } else { avatar->startMotion(it->motionId); // only start if not a kfm; then wait until it casts as a kfm avatar->stopMotion(it->motionId); // only stop if we have used it and we started it } allMotionsApplied &= it->motionApplied; } return allMotionsApplied; } void FSPoseState::resetPriorityForCaptureOrder(LLVOAvatar* avatar, FSPosingMotion* posingMotion, S32 captureOrder) { for (auto it = sMotionStates[avatar->getID()].begin(); it != sMotionStates[avatar->getID()].end(); it++) { if (it->jointNumbersAnimated.empty()) continue; if (it->motionApplied) continue; if (it->captureOrder != captureOrder) continue; posingMotion->resetBonePriority(it->jointNumbersAnimated); } } bool FSPoseState::canSaveMotionId(LLVOAvatar* avatarPlayingMotionId, LLAssetID motionId) { if (!gAgentAvatarp || gAgentAvatarp.isNull()) return false; if (sMotionStatesOwnedByMe[motionId]) return true; // does the animation exist in inventory LLInventoryItem* item = gInventory.getItem(motionId); if (item && item->getPermissions().getOwner() == gAgentAvatarp->getID()) { sMotionStatesOwnedByMe[motionId] = true; return sMotionStatesOwnedByMe[motionId]; } if (!avatarPlayingMotionId) return false; if (avatarPlayingMotionId->getID() == gAgentAvatarp->getID()) return motionIdIsAgentAnimationSource(motionId); return motionIdIsFromPrimAgentOwnsAgentIsSittingOn(avatarPlayingMotionId, motionId); } bool FSPoseState::motionIdIsAgentAnimationSource(LLAssetID motionId) { if (!gAgentAvatarp || gAgentAvatarp.isNull()) return false; for (const auto& [anim_object_id, anim_anim_id] : gAgentAvatarp->mAnimationSources) { if (anim_anim_id != motionId) continue; // is the item that started the anim in inventory LLInventoryItem* item = gInventory.getItem(anim_object_id); if (item && item->getPermissions().getOwner() == gAgentAvatarp->getID()) { sMotionStatesOwnedByMe[motionId] = true; return sMotionStatesOwnedByMe[motionId]; } // is the item that start the animation in-world LLViewerObject* object = gObjectList.findObject(anim_object_id); if (object && object->permYouOwner()) { sMotionStatesOwnedByMe[motionId] = true; return sMotionStatesOwnedByMe[motionId]; } } return false; } bool FSPoseState::motionIdIsFromPrimAgentOwnsAgentIsSittingOn(LLVOAvatar* avatarPlayingMotionId, LLAssetID motionId) { if (!avatarPlayingMotionId) return false; const LLViewerObject* agentRoot = dynamic_cast(avatarPlayingMotionId->getRoot()); if (!agentRoot) return false; const LLUUID& assetIdTheyAreSittingOn = agentRoot->getID(); if (assetIdTheyAreSittingOn == avatarPlayingMotionId->getID()) return false; // they are not sitting on a thing LLViewerObject* object = gObjectList.findObject(assetIdTheyAreSittingOn); if (!object || !object->permYouOwner()) return false; // gAgent does not own what they are sitting on if (object->isInventoryPending()) return false; if (object->isInventoryDirty() || !object->getInventoryRoot()) { object->requestInventory(); return false; // whatever they are sitting on, we don't have the inventory list for yet } LLInventoryItem* item = object->getInventoryItemByAsset(motionId, LLAssetType::AT_ANIMATION); if (item && item->getPermissions().getOwner() == gAgentAvatarp->getID()) { sMotionStatesOwnedByMe[motionId] = true; return sMotionStatesOwnedByMe[motionId]; } return false; } bool FSPoseState::vector2IsSubsetOfVector1(std::vector newRecapture, std::vector oldRecapture) { if (newRecapture.empty()) return false; if (oldRecapture.empty()) return false; if (newRecapture.size() < oldRecapture.size()) return false; for (S32 number : oldRecapture) if (std::find(newRecapture.begin(), newRecapture.end(), number) == newRecapture.end()) return false; return true; } std::string FSPoseState::encodeVectorToString(const std::vector& vector) { std::string encoded = ""; if (vector.empty()) return encoded; for (S32 numberToEncode : vector) { if (numberToEncode > 251) // max 216 at time of writing continue; S32 number = numberToEncode; if (number >= 189) { encoded += "~"; number -= 189; } if (number >= 126) { encoded += "}"; number -= 126; } if (number >= 63) { encoded += "|"; number -= 63; } encoded += char(number + int('?')); } return encoded; } std::vector FSPoseState::decodeStringToVector(std::string_view vector) { std::vector decoded; if (vector.empty()) return decoded; S32 number = 0; for (char ch : vector) { if (ch > '~' || ch < '?') continue; if (ch == '~') { number += 189; continue; } if (ch == '}') { number += 126; continue; } if (ch == '|') { number += 63; continue; } number -= int('?'); number += S32(ch); decoded.push_back(number); number = 0; } return decoded; }