Forked from
Alchemy Viewer / Alchemy Viewer
1925 commits behind, 98 commits ahead of the upstream repository.
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
bdfloaterposecreator.cpp 108.90 KiB
/**
*
* Copyright (C) 2018, NiranV Dean
*
* 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.
*
*/
#include "llviewerprecompiledheaders.h"
#include "bdfloaterposecreator.h"
#include "lluictrlfactory.h"
#include "llagent.h"
#include "llavatarname.h"
#include "llavatarnamecache.h"
#include "llclipboard.h"
#include "lldatapacker.h"
#include "lldiriterator.h"
#include "llfilepicker.h"
#include "llfilesystem.h"
#include "llkeyframemotion.h"
#include "llnotificationsutil.h"
#include "llmenugl.h"
#include "llmenubutton.h"
#include "lltoggleablemenu.h"
#include "llviewermenu.h"
#include "llsdserialize.h"
#include "llsdutil.h"
#include "llviewerjointattachment.h"
#include "llviewerjoint.h"
#include "llvoavatarself.h"
#include "pipeline.h"
#include "llviewerobjectlist.h"
#include "lldrawpoolavatar.h"
//BD - Animesh Support
#include "llcontrolavatar.h"
//BD - Black Dragon specifics
#include "bdanimator.h"
#include "bdposingmotion.h"
#include "bdfloaterposer.h"
//BD - 1 frame is always 1/60 of a second, we assume animations run at 60 FPS by default.
// I might add an option to change it but right now its all manual work.
const F32 FRAMETIME = 1.f / 60.f;
BDFloaterPoseCreator::BDFloaterPoseCreator(const LLSD& key)
: LLFloater(key),
mJointScrolls()
{
//BD - Save our current pose as XML or ANIM file to be used or uploaded later.
mCommitCallbackRegistrar.add("Pose.Save", boost::bind(&BDFloaterPoseCreator::onClickPoseSave, this, _2));
//BD - Start our custom pose.
mCommitCallbackRegistrar.add("Pose.Start", boost::bind(&BDFloaterPoseCreator::onPoseStart, this));
//BD - Import ANIM file to the poser.
mCommitCallbackRegistrar.add("Pose.Import", boost::bind(&BDFloaterPoseCreator::onPoseImport, this));
//BD - Start/Stop the pose to preview it.
mCommitCallbackRegistrar.add("Pose.StartStop", boost::bind(&BDFloaterPoseCreator::onPoseStartStop, this));
//BD - Multipurpose function to edit several basic parameters in the animation.
mCommitCallbackRegistrar.add("Pose.EditInfo", boost::bind(&BDFloaterPoseCreator::onEditAnimationInfo, this, _2));
//BD - Multipurpose function to edit several basic parameters in the animation.
mCommitCallbackRegistrar.add("Pose.Interpolation", boost::bind(&BDFloaterPoseCreator::onInterpolationChange, this, _1));
//BD - Add a keyframe to the motion curve.
mCommitCallbackRegistrar.add("Keyframe.Add", boost::bind(&BDFloaterPoseCreator::onKeyframeAdd, this));
//BD - Remove a keyframe from the motion curve.
mCommitCallbackRegistrar.add("Keyframe.Remove", boost::bind(&BDFloaterPoseCreator::onKeyframeRemove, this));
//BD - Change a keyframe's time value.
mCommitCallbackRegistrar.add("Keyframe.Time", boost::bind(&BDFloaterPoseCreator::onKeyframeTime, this));
//BD - Refresh the keyframe scroll list and fill it with all relevant keys.
mCommitCallbackRegistrar.add("Keyframe.Refresh", boost::bind(&BDFloaterPoseCreator::onKeyframeRefresh, this));
//BD - Change a bone's rotation.
mCommitCallbackRegistrar.add("Joint.Set", boost::bind(&BDFloaterPoseCreator::onJointSet, this, _1, _2));
//BD - Change a bone's position.
mCommitCallbackRegistrar.add("Joint.PosSet", boost::bind(&BDFloaterPoseCreator::onJointPosSet, this, _1, _2));
//BD - Change a bone's scale.
mCommitCallbackRegistrar.add("Joint.SetScale", boost::bind(&BDFloaterPoseCreator::onJointScaleSet, this, _1, _2));
//BD - Add or remove a joint state to or from the pose (enable/disable our overrides).
mCommitCallbackRegistrar.add("Joint.ChangeState", boost::bind(&BDFloaterPoseCreator::onJointChangeState, this));
//BD - Reset all selected bone rotations and positions.
mCommitCallbackRegistrar.add("Joint.ResetJointFull", boost::bind(&BDFloaterPoseCreator::onJointRotPosScaleReset, this));
//BD - Reset all selected bone rotations back to 0,0,0.
mCommitCallbackRegistrar.add("Joint.ResetJointRotation", boost::bind(&BDFloaterPoseCreator::onJointRotationReset, this));
//BD - Reset all selected bones positions back to their default.
mCommitCallbackRegistrar.add("Joint.ResetJointPosition", boost::bind(&BDFloaterPoseCreator::onJointPositionReset, this));
//BD - Reset all selected bones scales back to their default.
mCommitCallbackRegistrar.add("Joint.ResetJointScale", boost::bind(&BDFloaterPoseCreator::onJointScaleReset, this));
//BD - Reset all selected bone rotations back to the initial rotation.
mCommitCallbackRegistrar.add("Joint.RevertJointRotation", boost::bind(&BDFloaterPoseCreator::onJointRotationRevert, this));
//BD - Mirror the current bone's rotation to match what the other body side's rotation should be.
mCommitCallbackRegistrar.add("Joint.Mirror", boost::bind(&BDFloaterPoseCreator::onJointMirror, this));
//BD - Copy and mirror the other body side's bone rotation.
mCommitCallbackRegistrar.add("Joint.Symmetrize", boost::bind(&BDFloaterPoseCreator::onJointSymmetrize, this));
//BD - Toggle Mirror Mode on/off.
mCommitCallbackRegistrar.add("Joint.ToggleMirror", boost::bind(&BDFloaterPoseCreator::toggleMirrorMode, this, _1));
//BD - Toggle Easy Rotation on/off.
mCommitCallbackRegistrar.add("Joint.EasyRotations", boost::bind(&BDFloaterPoseCreator::toggleEasyRotations, this, _1));
//BD - Flip pose (mirror).
mCommitCallbackRegistrar.add("Joint.FlipPose", boost::bind(&BDFloaterPoseCreator::onFlipPose, this));
}
BDFloaterPoseCreator::~BDFloaterPoseCreator()
{
}
BOOL BDFloaterPoseCreator::postBuild()
{
std::string pathname = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "animations");
if (!gDirUtilp->fileExists(pathname))
{
LLFile::mkdir(pathname);
}
//BD - Posing
mJointScrolls = { { this->getChild<LLScrollListCtrl>("joints_scroll", true),
this->getChild<LLScrollListCtrl>("cv_scroll", true),
this->getChild<LLScrollListCtrl>("attach_scroll", true) } };
mJointScrolls[JOINTS]->setCommitOnSelectionChange(TRUE);
mJointScrolls[JOINTS]->setCommitCallback(boost::bind(&BDFloaterPoseCreator::onKeyframeRefresh, this));
mJointScrolls[JOINTS]->setDoubleClickCallback(boost::bind(&BDFloaterPoseCreator::onJointChangeState, this));
//BD - Collision Volumes
mJointScrolls[COLLISION_VOLUMES]->setCommitOnSelectionChange(TRUE);
mJointScrolls[COLLISION_VOLUMES]->setCommitCallback(boost::bind(&BDFloaterPoseCreator::onKeyframeRefresh, this));
//BD - Attachment Bones
mJointScrolls[ATTACHMENT_BONES]->setCommitOnSelectionChange(TRUE);
mJointScrolls[ATTACHMENT_BONES]->setCommitCallback(boost::bind(&BDFloaterPoseCreator::onKeyframeRefresh, this));
mKeyframeScroll = this->getChild<LLScrollListCtrl>("keyframe_scroll", true);
mKeyframeScroll->setCommitOnSelectionChange(TRUE);
mKeyframeScroll->setCommitCallback(boost::bind(&BDFloaterPoseCreator::onKeyframeSelect, this));
mTimelineScroll = this->getChild<LLScrollListCtrl>("timeframe_scroll", true);
mRotationSliders = { { getChild<LLUICtrl>("Rotation_X"), getChild<LLUICtrl>("Rotation_Y"), getChild<LLUICtrl>("Rotation_Z") } };
mPositionSliders = { { getChild<LLSliderCtrl>("Position_X"), getChild<LLSliderCtrl>("Position_Y"), getChild<LLSliderCtrl>("Position_Z") } };
mScaleSliders = { { getChild<LLSliderCtrl>("Scale_X"), getChild<LLSliderCtrl>("Scale_Y"), getChild<LLSliderCtrl>("Scale_Z") } };
mJointTabs = getChild<LLTabContainer>("joints_tabs");
mJointTabs->setCommitCallback(boost::bind(&BDFloaterPoseCreator::onJointControlsRefresh, this));
mModifierTabs = getChild<LLTabContainer>("modifier_tabs");
mModifierTabs->setCommitCallback(boost::bind(&BDFloaterPoseCreator::onKeyframeRefresh, this));
//BD - Misc
mDelayRefresh = false;
mMirrorMode = false;
mEasyRotations = true;
mAutoDuration = true;
mStartPosingBtn = getChild<LLButton>("activate");
//BD - Poser Menu
LLUICtrl::CommitCallbackRegistry::ScopedRegistrar pose_reg;
LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar;
//BD - Poser Right Click Menu
pose_reg.add("Joints.Menu", boost::bind(&BDFloaterPoseCreator::onJointContextMenuAction, this, _2));
enable_registrar.add("Joints.OnEnable", boost::bind(&BDFloaterPoseCreator::onJointContextMenuEnable, this, _2));
LLContextMenu* joint_menu = LLUICtrlFactory::getInstance()->createFromFile<LLContextMenu>("menu_poser_joints.xml",
gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance());
mJointScrolls[JOINTS]->setContextMenu(LLScrollListCtrl::MENU_EXTERNAL, joint_menu);
return TRUE;
}
void BDFloaterPoseCreator::draw()
{
LLFloater::draw();
}
void BDFloaterPoseCreator::onOpen(const LLSD& key)
{
//BD - Check whether we should delay the default value collection or fire it immediately.
mDelayRefresh = !gAgentAvatarp->isFullyLoaded();
if (!mDelayRefresh)
{
onCollectDefaults();
}
onJointRotPosScaleReset();
onJointRefresh();
}
void BDFloaterPoseCreator::onClose(bool app_quitting)
{
//BD - Doesn't matter because we destroy the window and rebuild it every time we open it anyway.
mJointScrolls[JOINTS]->clearRows();
mJointScrolls[COLLISION_VOLUMES]->clearRows();
mJointScrolls[ATTACHMENT_BONES]->clearRows();
}
////////////////////////////////
//BD - Keyframes
////////////////////////////////
void BDFloaterPoseCreator::onKeyframesRebuild()
{
if (!gDragonAnimator.mPoseCreatorMotion)
return;
//BD - Now write an entry with all given information into our list so we can use it.
LLKeyframeMotion::JointMotionList* joint_list = gDragonAnimator.mPoseCreatorMotion->getJointMotionList();
if (!joint_list)
return;
mKeyframeScroll->clearRows();
mTimelineScroll->clearRows();
for(S32 joint_idx = 0; joint_idx < joint_list->getNumJointMotions(); joint_idx++)
{
LLKeyframeMotion::JointMotion* joint_motion = joint_list->getJointMotion(joint_idx);
if (!joint_motion)
return;
LLKeyframeMotion::RotationCurve rot_curve = joint_motion->mRotationCurve;
LLKeyframeMotion::PositionCurve pos_curve = joint_motion->mPositionCurve;
LLKeyframeMotion::ScaleCurve scale_curve = joint_motion->mScaleCurve;
LLScrollListMultiSlider* col_slider = nullptr;
if (!rot_curve.mKeys.empty()
|| !pos_curve.mKeys.empty()
|| !scale_curve.mKeys.empty())
{
LLSD slider;
const std::string joint_name = joint_motion->mJointName;
slider["columns"][0]["column"] = "joint";
slider["columns"][0]["value"] = joint_name;
slider["columns"][1]["column"] = "multislider";
slider["columns"][1]["type"] = "multislider";
slider["columns"][1]["min_value"] = 0;
slider["columns"][1]["max_value"] = 3600;
slider["columns"][1]["increment"] = 1.f;
LLScrollListItem* element = mTimelineScroll->addElement(slider);
col_slider = (LLScrollListMultiSlider*)element->getColumn(1);
}
for (auto& rot_key : rot_curve.mKeys)
{
F32 roll, pitch, yaw;
LLQuaternion rot_quat = rot_key.second.mValue;
rot_quat.getEulerAngles(&roll, &pitch, &yaw);
S32 time_frames = (S32)ll_round(rot_key.second.mTime / FRAMETIME, 1.f);
if (col_slider)
{
//F32 time_key = time_frames / col_slider->getMaxValue();
col_slider->addKeyframe(time_frames, (LLSD)rot_key.first);
}
}
for (auto& pos_key : pos_curve.mKeys)
{
LLVector3 pos = pos_key.second.mValue;
S32 time_frames = (S32)ll_round(pos_key.second.mTime / FRAMETIME, 1.f);
if (col_slider)
{
//F32 time_key = time_frames / col_slider->getMaxValue();
col_slider->addKeyframe(time_frames, (LLSD)pos_key.first);
}
}
for (auto& scale_key : scale_curve.mKeys)
{
LLVector3 scale = scale_key.second.mValue;
S32 time_frames = (S32)ll_round(scale_key.second.mTime / FRAMETIME, 1.f);
if (col_slider)
{
//F32 time_key = time_frames / col_slider->getMaxValue();
col_slider->addKeyframe(time_frames, (LLSD)scale_key.first);
}
}
}
}
void BDFloaterPoseCreator::onKeyframeRefresh()
{
S32 idx = mJointTabs->getCurrentPanelIndex();
LLScrollListItem* item = mJointScrolls[idx]->getFirstSelected();
if (!item)
return;
if (!item->getEnabled())
return;
LLJoint* joint = (LLJoint*)item->getUserdata();
if (!joint)
return;
if (!gDragonAnimator.mPoseCreatorMotion)
return;
//BD - Now write an entry with all given information into our list so we can use it.
LLKeyframeMotion::JointMotionList* joint_list = gDragonAnimator.mPoseCreatorMotion->getJointMotionList();
if (!joint_list)
return;
LLKeyframeMotion::JointMotion* joint_motion = joint_list->getJointMotion(joint->getJointNum());
if (!joint_motion)
return;
mKeyframeScroll->clearRows();
LLKeyframeMotion::RotationCurve rot_curve = joint_motion->mRotationCurve;
LLKeyframeMotion::PositionCurve pos_curve = joint_motion->mPositionCurve;
LLKeyframeMotion::ScaleCurve scale_curve = joint_motion->mScaleCurve;
LLScrollListMultiSlider* col_slider = nullptr;
if (!rot_curve.mKeys.empty()
|| !pos_curve.mKeys.empty()
|| !scale_curve.mKeys.empty())
{
//BD - Find and delete the current entry.
for (auto timeline_item : mTimelineScroll->getAllData())
{
std::string name = timeline_item->getColumn(0)->getValue();
if (name == joint->getName())
{
timeline_item->setFlagged(true);
break;
}
}
mTimelineScroll->deleteFlaggedItems();
//BD - Now rewrite the entry.
LLSD slider;
const std::string joint_name = joint->getName();
slider["columns"][0]["column"] = "joint";
slider["columns"][0]["value"] = joint_name;
slider["columns"][1]["column"] = "multislider";
slider["columns"][1]["type"] = "multislider";
slider["columns"][1]["min_value"] = 0;
slider["columns"][1]["max_value"] = 3600;
slider["columns"][1]["increment"] = 1.f;
LLScrollListItem* element = mTimelineScroll->addElement(slider);
col_slider = (LLScrollListMultiSlider*)element->getColumn(1);
}
S32 modidx = mModifierTabs->getCurrentPanelIndex();
if (modidx == 0)
{
for (auto& rot_key : rot_curve.mKeys)
{
F32 roll, pitch, yaw;
LLQuaternion rot_quat = rot_key.second.mValue;
rot_quat.getEulerAngles(&roll, &pitch, &yaw);
S32 time_frames = (S32)ll_round(rot_key.second.mTime / FRAMETIME, 1.f);
LLSD row;
row["columns"][0]["column"] = "time";
row["columns"][0]["value"] = ll_round(time_frames, 1.f);
row["columns"][1]["column"] = "value";
row["columns"][1]["value"] = rot_key.first;
row["columns"][2]["column"] = "x";
row["columns"][2]["value"] = ll_round(roll, 0.001f);
row["columns"][3]["column"] = "y";
row["columns"][3]["value"] = ll_round(pitch, 0.001f);
row["columns"][4]["column"] = "z";
row["columns"][4]["value"] = ll_round(yaw, 0.001f);
mKeyframeScroll->addElement(row);
if (col_slider)
{
//F32 time_key = time_frames / col_slider->getMaxValue();
col_slider->addKeyframe(time_frames, (LLSD)rot_key.first);
}
}
}
else if (modidx == 1)
{
for (auto& pos_key : pos_curve.mKeys)
{
LLVector3 pos = pos_key.second.mValue;
S32 time_frames = (S32)ll_round(pos_key.second.mTime / FRAMETIME, 1.f);
LLSD row;
row["columns"][0]["column"] = "time";
row["columns"][0]["value"] = ll_round(time_frames, 1.f);
row["columns"][1]["column"] = "value";
row["columns"][1]["value"] = pos_key.first;
row["columns"][2]["column"] = "x";
row["columns"][2]["value"] = ll_round(pos.mV[VX], 0.001f);
row["columns"][3]["column"] = "y";
row["columns"][3]["value"] = ll_round(pos.mV[VY], 0.001f);
row["columns"][4]["column"] = "z";
row["columns"][4]["value"] = ll_round(pos.mV[VZ], 0.001f);
mKeyframeScroll->addElement(row);
if (col_slider)
{
//F32 time_key = time_frames / col_slider->getMaxValue();
col_slider->addKeyframe(time_frames, (LLSD)pos_key.first);
}
}
}
else if (modidx == 2)
{
for (auto& scale_key : scale_curve.mKeys)
{
LLVector3 scale = scale_key.second.mValue;
S32 time_frames = (S32)ll_round(scale_key.second.mTime / FRAMETIME, 1.f);
LLSD row;
row["columns"][0]["column"] = "time";
row["columns"][0]["value"] = ll_round(time_frames, 1.f);
row["columns"][1]["column"] = "value";
row["columns"][1]["value"] = scale_key.first;
row["columns"][2]["column"] = "x";
row["columns"][2]["value"] = ll_round(scale.mV[VX], 0.001f);
row["columns"][3]["column"] = "y";
row["columns"][3]["value"] = ll_round(scale.mV[VY], 0.001f);
row["columns"][4]["column"] = "z";
row["columns"][4]["value"] = ll_round(scale.mV[VZ], 0.001f);
mKeyframeScroll->addElement(row);
if (col_slider)
{
//F32 time_key = time_frames / col_slider->getMaxValue();
col_slider->addKeyframe(time_frames, (LLSD)scale_key.first);
}
}
}
//BD - Always select the last entry whenever we switch bones to allow quickly making
// changes or adding new keyframes.
mKeyframeScroll->selectNthItem(mKeyframeScroll->getItemCount() - 1);
onJointControlsRefresh();
onKeyframeSelect();
}
void BDFloaterPoseCreator::onKeyframeSelect()
{
LLScrollListItem* item = mKeyframeScroll->getFirstSelected();
if (!item)
return;
LLScrollListItem* joint_item = mJointScrolls[JOINTS]->getFirstSelected();
if (!joint_item)
return;
LLJoint* joint = (LLJoint*)joint_item->getUserdata();
if (!joint)
return;
if (!gDragonAnimator.mPoseCreatorMotion)
return;
LLKeyframeMotion::JointMotionList* joint_list = gDragonAnimator.mPoseCreatorMotion->getJointMotionList();
LLKeyframeMotion::JointMotion* joint_motion = joint_list->getJointMotion(joint->getJointNum());
S32 si = item->getColumn(1)->getValue().asInteger();
S32 idx = mModifierTabs->getCurrentPanelIndex();
if (idx == 0)
{
//BD - Find the key we are looking for and remove it.
for (auto& it : joint_motion->mRotationCurve.mKeys)
{
if ((S32)it.first == si)
{
LLQuaternion rot_quat = it.second.mValue;
LLVector3 rot_vec;
rot_quat.getEulerAngles(&rot_vec.mV[VX], &rot_vec.mV[VY], &rot_vec.mV[VZ]);
mRotationSliders[VX]->setValue(rot_vec.mV[VX]);
mRotationSliders[VY]->setValue(rot_vec.mV[VY]);
mRotationSliders[VZ]->setValue(rot_vec.mV[VZ]);
joint->setTargetRotation(rot_quat);
}
}
}
else if (idx == 1)
{
for (auto& it : joint_motion->mPositionCurve.mKeys)
{
if ((S32)it.first == si)
{
LLVector3 pos = it.second.mValue;
mPositionSliders[VX]->setValue(pos.mV[VX]);
mPositionSliders[VY]->setValue(pos.mV[VY]);
mPositionSliders[VZ]->setValue(pos.mV[VZ]);
joint->setTargetPosition(pos);
}
}
}
else if (idx == 2)
{
for (auto& it : joint_motion->mScaleCurve.mKeys)
{
if ((S32)it.first == si)
{
LLVector3 scale = it.second.mValue;
mScaleSliders[VX]->setValue(scale.mV[VX]);
mScaleSliders[VY]->setValue(scale.mV[VY]);
mScaleSliders[VZ]->setValue(scale.mV[VZ]);
joint->setScale(scale);
}
}
}
}
void BDFloaterPoseCreator::onKeyframeAdd(F32 time, LLJoint* joint)
{
LLKeyframeMotion::JointMotionList* joint_list = gDragonAnimator.mPoseCreatorMotion->getJointMotionList();
S32 modifier_idx = mModifierTabs->getCurrentPanelIndex();
if (joint)
{
LLKeyframeMotion::JointMotion* joint_motion = joint_list->getJointMotion(joint->getJointNum());
if (modifier_idx == 0)
{
//BD - Create a rotation key and put it into the rotation curve.
LLKeyframeMotion::RotationKey rotation_key = LLKeyframeMotion::RotationKey(ll_round(time, 0.001f), joint->getTargetRotation());
//BD - Increase the key number since we are adding another key.
joint_motion->mRotationCurve.mNumKeys++;
joint_motion->mRotationCurve.mKeys[joint_motion->mRotationCurve.mNumKeys] = rotation_key;
LLJointState* joint_state = gDragonAnimator.mPoseCreatorMotion->findJointState(joint);
if (joint_state->getUsage() ^ LLJointState::ROT)
{
joint_state->setUsage(joint_state->getUsage() | LLJointState::ROT);
}
}
else if (modifier_idx == 1)
{
//BD - Create a position key and put it into the position curve.
LLKeyframeMotion::PositionKey position_key = LLKeyframeMotion::PositionKey(ll_round(time, 0.001f), joint->getTargetPosition());
//BD - Increase the key number since we are adding another key.
joint_motion->mPositionCurve.mNumKeys++;
joint_motion->mPositionCurve.mKeys[joint_motion->mPositionCurve.mNumKeys] = position_key;
LLJointState* joint_state = gDragonAnimator.mPoseCreatorMotion->findJointState(joint);
if (joint_state->getUsage() ^ LLJointState::POS)
{
joint_state->setUsage(joint_state->getUsage() | LLJointState::POS);
}
}
else if (modifier_idx == 2)
{
//BD - Create a scale key and put it into the scale curve.
LLKeyframeMotion::ScaleKey scale_key = LLKeyframeMotion::ScaleKey(ll_round(time, 0.001f), joint->getScale());
//BD - Increase the key number since we are adding another key.
joint_motion->mScaleCurve.mNumKeys++;
joint_motion->mScaleCurve.mKeys[joint_motion->mScaleCurve.mNumKeys] = scale_key;
LLJointState* joint_state = gDragonAnimator.mPoseCreatorMotion->findJointState(joint);
if (joint_state->getUsage() ^ LLJointState::SCALE)
{
joint_state->setUsage(joint_state->getUsage() | LLJointState::SCALE);
}
}
}
//BD - Check if our animation duration needs changing.
onAnimationDurationCheck();
}
void BDFloaterPoseCreator::onKeyframeAdd()
{
std::vector<LLScrollListItem*> items = mJointScrolls[JOINTS]->getAllSelected();
LLKeyframeMotion::JointMotionList* joint_list = gDragonAnimator.mPoseCreatorMotion->getJointMotionList();
LLScrollListItem* item = mKeyframeScroll->getFirstSelected();
MASK mask = gKeyboard->currentMask(TRUE);
bool multiple = (items.size() > 1);
S32 new_selected_idx = (mask == MASK_SHIFT || multiple) ? mKeyframeScroll->getChildCount() - 1 : mKeyframeScroll->getFirstSelectedIndex() + 1;
S32 modifier_idx = mModifierTabs->getCurrentPanelIndex();
for (auto joint_item : items)
{
LLJoint* joint = (LLJoint*)joint_item->getUserdata();
if (!joint)
continue;
LLKeyframeMotion::JointMotion* joint_motion = joint_list->getJointMotion(joint->getJointNum());
S32 selected_idx = 0;
S32 count = 1;
S32 keys = 1;
F32 time = FRAMETIME;
if (item)
selected_idx = item->getColumn(1)->getValue().asInteger();
if (modifier_idx == 0)
{
//BD - Put the key at the end of the list if SHIFT is held while pressing the add key button.
if (multiple || joint_motion->mRotationCurve.mNumKeys == 0 || gKeyboard->currentMask(TRUE) == MASK_SHIFT)
{
//BD - Add a bit of time to differentiate the keys.
// This also kinda determines if this is the first keyframe or not. If we can't find a previous
// entry we assume its the first keyframe, so simply use 1.f.
if (joint_motion->mRotationCurve.mNumKeys > 0)
time = joint_motion->mRotationCurve.mKeys[joint_motion->mRotationCurve.mNumKeys].mTime + FRAMETIME;
//BD - Create a rotation key and put it into the rotation curve.
LLKeyframeMotion::RotationKey rotation_key = LLKeyframeMotion::RotationKey(ll_round(time, 0.001f), joint->getTargetRotation());
//BD - Increase the key number since we are adding another key.
joint_motion->mRotationCurve.mNumKeys++;
joint_motion->mRotationCurve.mKeys[joint_motion->mRotationCurve.mNumKeys] = rotation_key;
}
//BD - Conversely put the key at the start of the list if CTRL is held while adding a key.
else if (gKeyboard->currentMask(TRUE) == MASK_CONTROL)
{
LLKeyframeMotion::RotationCurve rot_curve;
//BD - Create a rotation key and put it into the rotation curve.
LLKeyframeMotion::RotationKey rotation_key = LLKeyframeMotion::RotationKey(ll_round(time, 0.001f), joint->getTargetRotation());
rot_curve.mNumKeys++;
rot_curve.mKeys[count] = rotation_key;
count++;
//BD - We have to rewrite the entire rotation curve in order to push a key to the beginning.
for (auto& it : joint_motion->mRotationCurve.mKeys)
{
rot_curve.mNumKeys++;
rot_curve.mKeys[count] = it.second;
count++;
}
joint_motion->mRotationCurve = rot_curve;
}
//BD - Put the key after whatever is currently selected.
else
{
LLKeyframeMotion::RotationCurve rot_curve;
//BD - We have to rewrite the entire rotation curve in order to push a key inbetween others.
for (auto& it : joint_motion->mRotationCurve.mKeys)
{
rot_curve.mNumKeys++;
rot_curve.mKeys[count] = it.second;
count++;
if ((S32)it.first == selected_idx)
{
//BD - Add a bit of time to differentiate the keys.
// This also kinda determines if this is the first keyframe or not. If we can't find a previous
// entry we assume its the first keyframe, so simply use 1.f.
time = it.second.mTime + FRAMETIME;
//BD - Create a rotation key and put it into the rotation curve.
LLKeyframeMotion::RotationKey rotation_key = LLKeyframeMotion::RotationKey(ll_round(time, 0.001f), joint->getTargetRotation());
rot_curve.mNumKeys++;
rot_curve.mKeys[count] = rotation_key;
count++;
}
}
joint_motion->mRotationCurve = rot_curve;
}
keys = joint_motion->mRotationCurve.mNumKeys;
LLJointState* joint_state = gDragonAnimator.mPoseCreatorMotion->findJointState(joint);
if (joint_state->getUsage() ^ LLJointState::ROT)
{
joint_state->setUsage(joint_state->getUsage() | LLJointState::ROT);
}
}
else if (modifier_idx == 1)
{
//BD - Put the key at the end of the list if SHIFT is held while pressing the add key button.
if (multiple || joint_motion->mPositionCurve.mNumKeys == 0 || gKeyboard->currentMask(TRUE) == MASK_SHIFT)
{
//BD - Add a bit of time to differentiate the keys.
// This also kinda determines if this is the first keyframe or not. If we can't find a previous
// entry we assume its the first keyframe, so simply use 1.f.
if (joint_motion->mPositionCurve.mNumKeys > 0)
time = joint_motion->mPositionCurve.mKeys[joint_motion->mPositionCurve.mNumKeys].mTime + FRAMETIME;
//BD - Create a position key and put it into the position curve.
LLKeyframeMotion::PositionKey position_key = LLKeyframeMotion::PositionKey(ll_round(time, 0.001f), joint->getTargetPosition());
//BD - Increase the key number since we are adding another key.
joint_motion->mPositionCurve.mNumKeys++;
joint_motion->mPositionCurve.mKeys[joint_motion->mPositionCurve.mNumKeys] = position_key;
}
//BD - Conversely put the key at the start of the list if CTRL is held while adding a key.
else if (gKeyboard->currentMask(TRUE) == MASK_CONTROL)
{
LLKeyframeMotion::PositionCurve pos_curve;
//BD - Create a position key and put it into the position curve.
LLKeyframeMotion::PositionKey position_key = LLKeyframeMotion::PositionKey(ll_round(time, 0.001f), joint->getTargetPosition());
pos_curve.mNumKeys++;
pos_curve.mKeys[count] = position_key;
count++;
//BD - We have to rewrite the entire rotation curve in order to push a key to the beginning.
for (auto& it : joint_motion->mPositionCurve.mKeys)
{
pos_curve.mNumKeys++;
pos_curve.mKeys[count] = it.second;
count++;
}
joint_motion->mPositionCurve = pos_curve;
}
//BD - Put the key after whatever is currently selected.
else
{
LLKeyframeMotion::PositionCurve pos_curve;
//BD - We have to rewrite the entire rotation curve in order to push a key inbetween others.
for (auto& it : joint_motion->mPositionCurve.mKeys)
{
pos_curve.mNumKeys++;
pos_curve.mKeys[count] = it.second;
count++;
if ((S32)it.first == selected_idx)
{
//BD - Add a bit of time to differentiate the keys.
// This also kinda determines if this is the first keyframe or not. If we can't find a previous
// entry we assume its the first keyframe, so simply use 1.f.
time = it.second.mTime + FRAMETIME;
//BD - Create a position key and put it into the position curve.
LLKeyframeMotion::PositionKey position_key = LLKeyframeMotion::PositionKey(ll_round(time, 0.001f), joint->getTargetPosition());
pos_curve.mNumKeys++;
pos_curve.mKeys[count] = position_key;
count++;
}
}
joint_motion->mPositionCurve = pos_curve;
}
keys = joint_motion->mPositionCurve.mNumKeys;
LLJointState* joint_state = gDragonAnimator.mPoseCreatorMotion->findJointState(joint);
if (joint_state->getUsage() ^ LLJointState::POS)
{
joint_state->setUsage(joint_state->getUsage() | LLJointState::POS);
}
}
else if (modifier_idx == 2)
{
//BD - Create a scale key and put it into the scale curve.
//LLKeyframeMotion::ScaleKey scale_key = LLKeyframeMotion::ScaleKey(ll_round(time, 0.001f), joint->getScale());
//BD - Put the key at the end of the list if SHIFT is held while pressing the add key button.
if (multiple || joint_motion->mScaleCurve.mNumKeys == 0 || gKeyboard->currentMask(TRUE) == MASK_SHIFT)
{
//BD - Add a bit of time to differentiate the keys.
// This also kinda determines if this is the first keyframe or not. If we can't find a previous
// entry we assume its the first keyframe, so simply use 1.f.
if (joint_motion->mScaleCurve.mNumKeys > 0)
time = joint_motion->mScaleCurve.mKeys[joint_motion->mScaleCurve.mNumKeys].mTime + FRAMETIME;
//BD - Create a scale key and put it into the scale curve.
LLKeyframeMotion::ScaleKey scale_key = LLKeyframeMotion::ScaleKey(ll_round(time, 0.001f), joint->getScale());
//BD - Increase the key number since we are adding another key.
joint_motion->mScaleCurve.mNumKeys++;
joint_motion->mScaleCurve.mKeys[joint_motion->mScaleCurve.mNumKeys] = scale_key;
}
//BD - Conversely put the key at the start of the list if CTRL is held while adding a key.
else if (gKeyboard->currentMask(TRUE) == MASK_CONTROL)
{
LLKeyframeMotion::ScaleCurve scale_curve;
//BD - Create a scale key and put it into the scale curve.
LLKeyframeMotion::ScaleKey scale_key = LLKeyframeMotion::ScaleKey(ll_round(time, 0.001f), joint->getScale());
scale_curve.mNumKeys++;
scale_curve.mKeys[count] = scale_key;
count++;
//BD - We have to rewrite the entire rotation curve in order to push a key to the beginning.
for (auto& it : joint_motion->mScaleCurve.mKeys)
{
scale_curve.mNumKeys++;
scale_curve.mKeys[count] = it.second;
count++;
}
joint_motion->mScaleCurve = scale_curve;
}
//BD - Put the key after whatever is currently selected.
else
{
LLKeyframeMotion::ScaleCurve scale_curve;
//BD - We have to rewrite the entire rotation curve in order to push a key inbetween others.
for (auto& it : joint_motion->mScaleCurve.mKeys)
{
scale_curve.mNumKeys++;
scale_curve.mKeys[count] = it.second;
count++;
if ((S32)it.first == selected_idx)
{
//BD - Add a bit of time to differentiate the keys.
// This also kinda determines if this is the first keyframe or not. If we can't find a previous
// entry we assume its the first keyframe, so simply use 1.f.
time = it.second.mTime + FRAMETIME;
//BD - Create a scale key and put it into the scale curve.
LLKeyframeMotion::ScaleKey scale_key = LLKeyframeMotion::ScaleKey(ll_round(time, 0.001f), joint->getScale());
scale_curve.mNumKeys++;
scale_curve.mKeys[count] = scale_key;
count++;
}
}
joint_motion->mScaleCurve = scale_curve;
}
keys = joint_motion->mScaleCurve.mNumKeys;
LLJointState* joint_state = gDragonAnimator.mPoseCreatorMotion->findJointState(joint);
if (joint_state->getUsage() ^ LLJointState::SCALE)
{
joint_state->setUsage(joint_state->getUsage() | LLJointState::SCALE);
}
}
}
//BD - Refresh the keyframes.
onKeyframeRefresh();
//BD - Check if our animation duration needs changing.
onAnimationDurationCheck();
//BD - Always select the last entry whenever we switch bones to allow quickly making
// changes or adding new keyframes.
mKeyframeScroll->selectNthItem(new_selected_idx);
}
void BDFloaterPoseCreator::onKeyframeRemove()
{
LLScrollListItem* item = mKeyframeScroll->getFirstSelected();
if (!item)
return;
LLScrollListItem* joint_item = mJointScrolls[JOINTS]->getFirstSelected();
if (!joint_item)
return;
LLJoint* joint = (LLJoint*)joint_item->getUserdata();
if (!joint)
return;
if (!gDragonAnimator.mPoseCreatorMotion)
return;
LLKeyframeMotion::JointMotionList* joint_list = gDragonAnimator.mPoseCreatorMotion->getJointMotionList();
LLKeyframeMotion::JointMotion* joint_motion = joint_list->getJointMotion(joint->getJointNum());
LLJointState* joint_state = gDragonAnimator.mPoseCreatorMotion->findJointState(joint);
S32 idx = mModifierTabs->getCurrentPanelIndex();
S32 si = mKeyframeScroll->getFirstSelected()->getColumn(1)->getValue().asInteger();
S32 usage = joint_state->getUsage();
S32 count = 1;
S32 last_selected = mKeyframeScroll->getFirstSelectedIndex();
if (idx == 0)
{
LLKeyframeMotion::RotationCurve rot_curve;
//BD - Rather than removing the key, rewriting all index numbers or shoving keys around
// we simply opt to create a new curve, this automatically reorganizes and counts all
// keys putting them in their proper place making this a simple solution. Not exactly
// efficient but it hardly matters here.
for (auto& it : joint_motion->mRotationCurve.mKeys)
{
if((S32)it.first != si)
{
rot_curve.mKeys[count] = it.second;
rot_curve.mNumKeys = count;
count++;
}
}
joint_motion->mRotationCurve = rot_curve;
if (joint_motion->mRotationCurve.mNumKeys == 0)
{
if (usage & LLJointState::ROT)
{
usage &= !LLJointState::ROT;
joint_state->setUsage(usage);
}
}
}
else if (idx == 1)
{
LLKeyframeMotion::PositionCurve pos_curve;
for (auto& it : joint_motion->mPositionCurve.mKeys)
{
if ((S32)it.first != si)
{
pos_curve.mKeys[count] = it.second;
pos_curve.mNumKeys = count;
count++;
}
}
joint_motion->mPositionCurve = pos_curve;
if (joint_motion->mPositionCurve.mNumKeys == 0)
{
if (usage & LLJointState::POS)
{
usage &= !LLJointState::POS;
joint_state->setUsage(usage);
}
}
}
else if (idx == 2)
{
LLKeyframeMotion::ScaleCurve scale_curve;
for (auto& it : joint_motion->mScaleCurve.mKeys)
{
if ((S32)it.first != si)
{
scale_curve.mKeys[count] = it.second;
scale_curve.mNumKeys = count;
count++;
}
}
joint_motion->mScaleCurve = scale_curve;
if (joint_motion->mScaleCurve.mNumKeys == 0)
{
if (usage & LLJointState::SCALE)
{
usage &= !LLJointState::SCALE;
joint_state->setUsage(usage);
}
}
}
//BD - Refresh the keyframes.
onKeyframeRefresh();
//BD - Check if our animation duration needs changing.
onAnimationDurationCheck();
//BD - Select the next entry in line after deleting one.
mKeyframeScroll->selectNthItem(last_selected);
}
void BDFloaterPoseCreator::onKeyframeTime()
{
LLScrollListItem* item = mKeyframeScroll->getFirstSelected();
if (!item)
return;
LLScrollListItem* joint_item = mJointScrolls[JOINTS]->getFirstSelected();
if (!joint_item)
return;
LLJoint* joint = (LLJoint*)joint_item->getUserdata();
if (!joint)
return;
if (!gDragonAnimator.mPoseCreatorMotion)
return;
LLKeyframeMotion::JointMotionList* joint_list = gDragonAnimator.mPoseCreatorMotion->getJointMotionList();
LLKeyframeMotion::JointMotion* joint_motion = joint_list->getJointMotion(joint->getJointNum());
S32 si = mKeyframeScroll->getFirstSelectedIndex() + 1;
S32 idx = mModifierTabs->getCurrentPanelIndex();
//BD - Set times in frames rather than direct time, it's easier for the user.
// We assume 60 FPS for an animation by default.
S32 time_frames = getChild<LLUICtrl>("key_time")->getValue().asInteger();
F32 time_float = FRAMETIME * time_frames;
if (idx == 0)
{
//BD - Find the key we are looking for and remove it.
for (auto& it : joint_motion->mRotationCurve.mKeys)
{
if ((S32)it.first == si)
{
it.second.mTime = ll_round(time_float, 0.001f);
}
}
}
else if (idx == 1)
{
for (auto& it : joint_motion->mPositionCurve.mKeys)
{
if((S32)it.first == si)
{
it.second.mTime = ll_round(time_float, 0.001f);
}
}
}
else if (idx == 2)
{
for (auto& it : joint_motion->mScaleCurve.mKeys)
{
if ((S32)it.first == si)
{
it.second.mTime = ll_round(time_float, 0.001f);
}
}
}
onKeyframeRefresh();
mKeyframeScroll->selectNthItem(si - 1);
//item->getColumn(0)->setValue(time_frames);
onAnimationDurationCheck();
}
void BDFloaterPoseCreator::onEditAnimationInfo(const LLSD& param)
{
if (!gDragonAnimator.mPoseCreatorMotion)
return;
LLKeyframeMotion::JointMotionList* joint_list = gDragonAnimator.mPoseCreatorMotion->getJointMotionList();
std::string msg = param.asString();
if (msg == "ease_in")
{
joint_list->mEaseInDuration = getChild<LLUICtrl>("ease_in")->getValue().asReal();
}
else if (msg == "ease_out")
{
joint_list->mEaseOutDuration = getChild<LLUICtrl>("ease_out")->getValue().asReal();
}
else if (msg == "duration_auto")
{
mAutoDuration = getChild<LLUICtrl>("AutoDuration")->getValue().asBoolean();
onAnimationDurationCheck();
}
else if (msg == "duration")
{
joint_list->mDuration = getChild<LLUICtrl>("keyframe_duration")->getValue().asReal();
}
else if (msg == "loop")
{
joint_list->mLoop = getChild<LLUICtrl>("LoopAnimation")->getValue().asBoolean();
}
else if (msg == "loop_in")
{
joint_list->mLoopInPoint = getChild<LLUICtrl>("loop_in")->getValue().asReal();
}
else if (msg == "loop_out")
{
joint_list->mLoopOutPoint = getChild<LLUICtrl>("loop_out")->getValue().asReal();
}
else if (msg == "priority")
{
joint_list->mBasePriority = (LLJoint::JointPriority)getChild<LLUICtrl>("base_priority")->getValue().asInteger();
}
else if (msg == "hand")
{
joint_list->mHandPose = (LLHandMotion::eHandPose)getChild<LLUICtrl>("hand_poses")->getValue().asInteger();
}
}
void BDFloaterPoseCreator::onInterpolationChange(LLUICtrl* ctrl)
{
LLScrollListItem* joint_item = mJointScrolls[JOINTS]->getFirstSelected();
if (!joint_item)
return;
LLJoint* joint = (LLJoint*)joint_item->getUserdata();
if (!joint)
return;
if (!gDragonAnimator.mPoseCreatorMotion)
return;
LLKeyframeMotion::JointMotionList* joint_list = gDragonAnimator.mPoseCreatorMotion->getJointMotionList();
LLKeyframeMotion::JointMotion* joint_motion = joint_list->getJointMotion(joint->getJointNum());
S32 interp_idx = ctrl->getValue().asInteger();
S32 idx = mModifierTabs->getCurrentPanelIndex();
if (idx == 0)
{
joint_motion->mRotationCurve.mInterpolationType = (LLKeyframeMotion::InterpolationType)interp_idx;
}
else if (idx == 1)
{
joint_motion->mPositionCurve.mInterpolationType = (LLKeyframeMotion::InterpolationType)interp_idx;
}
else if (idx == 2)
{
joint_motion->mScaleCurve.mInterpolationType = (LLKeyframeMotion::InterpolationType)interp_idx;
}
}
void BDFloaterPoseCreator::onAnimationDurationCheck()
{
if (!mAutoDuration)
return;
if (!gDragonAnimator.mPoseCreatorMotion)
return;
LLScrollListItem* joint_item = mJointScrolls[JOINTS]->getFirstSelected();
if (!joint_item)
return;
LLJoint* joint = (LLJoint*)joint_item->getUserdata();
if (!joint)
return;
LLKeyframeMotion::JointMotionList* joint_list = gDragonAnimator.mPoseCreatorMotion->getJointMotionList();
F32 new_time = 0.0f;
//BD - Go through all motions and all keys and determine the largest keyframe time.
for (auto& joint_motion : joint_list->mJointMotionArray)
{
for (auto& it : joint_motion->mRotationCurve.mKeys)
{
new_time = ll_round(llmax(new_time, it.second.mTime), 0.001f);
}
for (auto& it : joint_motion->mPositionCurve.mKeys)
{
new_time = ll_round(llmax(new_time, it.second.mTime), 0.001f);
}
for (auto& it : joint_motion->mScaleCurve.mKeys)
{
new_time = ll_round(llmax(new_time, it.second.mTime), 0.001f);
}
}
joint_list->mDuration = new_time;
joint_list->mLoopOutPoint = new_time;
getChild<LLUICtrl>("keyframe_duration")->setValue(new_time);
getChild<LLSpinCtrl>("loop_out")->setMaxValue(new_time);
getChild<LLUICtrl>("loop_out")->setValue(new_time);
}
////////////////////////////////
//BD - Poses
////////////////////////////////
void BDFloaterPoseCreator::onPoseStartStop()
{
BDPosingMotion* pmotion = (BDPosingMotion*)gAgentAvatarp->findMotion(ANIM_BD_POSING_MOTION);
LLKeyframeMotion* tmotion = (LLKeyframeMotion*)gAgentAvatarp->findMotion(mTempMotionID);
if(gDragonAnimator.mPoseCreatorMotion)
{
//BD - The reason we create a new preview motion here is because LLMotionController can deprecate and
// destroy our preview animation if we start and stop it and leave it stopped for a while. This
// would destroy all our work. Leaving the actual internal temp motion alive without ever running
// it prevents it from ever being put into the active list, thus never dropping it into the deactivated
// list when we stop it preventing it from being destroyed at some point.
if (!tmotion)
{
std::string full_path = gDirUtilp->getExpandedFilename(LL_PATH_ANIMATIONS, "_poser_temp.anim");
gDragonAnimator.mPoseCreatorMotion->dumpToFile(full_path);
LLKeyframeMotion* motion = onReadyTempMotion();
if (motion)
{
//BD - Start the newly created preview animation and set it to be eternal as
// we are going to continue editing it unless we start the preview again.
gAgentAvatarp->startMotion(motion->getID());
mTempMotionID = motion->getID();
}
}
else
{
if (tmotion->isStopped())
{
//BD - Save the animation temporarily.
std::string full_path = gDirUtilp->getExpandedFilename(LL_PATH_ANIMATIONS, "_poser_temp.anim");
gDragonAnimator.mPoseCreatorMotion->dumpToFile(full_path);
//BD - Reload the animation data back into our already existing animation.
// To refresh it and make sure its always up to date with all our changes.
LLKeyframeMotion* tmotion = onReadyTempMotion();
if (tmotion)
{
//BD - Start the newly created preview animation and set it to be eternal as
// we are going to continue editing it unless we start the preview again.
gAgentAvatarp->startMotion(tmotion->getID());
mTempMotionID = tmotion->getID();
}
}
else
{
gAgentAvatarp->stopMotion(tmotion->getID());
}
}
//BD - Stop our Poser motion if its active, start it again if we are still posing after disabling
// the preview.
if (gAgentAvatarp->mIsPosing)
{
if (!pmotion || pmotion->isStopped())
{
gAgentAvatarp->startMotion(ANIM_BD_POSING_MOTION);
//BD - We need to reset the skeleton here and reapply all the latest keyframes.
onJointRotPosScaleReset();
onPoseReapply();
}
else
{
gAgentAvatarp->stopMotion(ANIM_BD_POSING_MOTION);
}
}
}
}
bool BDFloaterPoseCreator::onClickPoseSave(const LLSD& param)
{
bool ret = onPoseExport();
if (ret)
LLNotificationsUtil::add("PoserExportANIMSuccess");
return ret;
}
void BDFloaterPoseCreator::onPoseReapply()
{
if(!gDragonAnimator.mPoseCreatorMotion)
return;
LLKeyframeMotion::JointMotionList* joint_list = gDragonAnimator.mPoseCreatorMotion->getJointMotionList();
if (!joint_list)
return;
for (auto& joint_motion : joint_list->mJointMotionArray)
{
LLJoint* joint = gAgentAvatarp->getJoint(joint_motion->mJointName);
if (joint)
{
LLKeyframeMotion::RotationCurve rot_curve = joint_motion->mRotationCurve;
LLKeyframeMotion::PositionCurve pos_curve = joint_motion->mPositionCurve;
LLKeyframeMotion::ScaleCurve scale_curve = joint_motion->mScaleCurve;
//BD - Load in the last keyframe of every bone and apply it to give us a
// start.
for (auto& rot_key : rot_curve.mKeys)
{
LLQuaternion rot = rot_key.second.mValue;
joint->setTargetRotation(rot);
}
for (auto& pos_key : pos_curve.mKeys)
{
LLVector3 pos = pos_key.second.mValue;
joint->setTargetPosition(pos);
}
for (auto& scale_key : scale_curve.mKeys)
{
LLVector3 scale = scale_key.second.mValue;
joint->setScale(scale);
}
//BD - We support per bone priority. Question is does SL?
//LLJoint::JointPriority priority = joint_motion->mPriority;
}
}
}
void BDFloaterPoseCreator::onPoseStart()
{
BDPosingMotion* motion = (BDPosingMotion*)gAgentAvatarp->findMotion(ANIM_BD_POSING_MOTION);
if (!motion || motion->isStopped())
{
gAgentAvatarp->setPosing();
//BD - Grab our current defaults to revert to them when stopping the Poser.
if(gAgentAvatarp->isFullyLoaded())
onCollectDefaults();
gAgent.stopFidget();
gAgentAvatarp->startMotion(ANIM_BD_POSING_MOTION);
onJointRotPosScaleReset();
//BD - Do not recreate the pose, keep the animation so we can return to it.
if(!gDragonAnimator.mPoseCreatorMotion)
{
//BD - Create the temp posing motion that we will use for the animation.
onCreateTempMotion();
gDragonAnimator.mPoseCreatorMotion = onReadyTempMotion("_poser_temp.anim" ,true);
}
}
else
{
//BD - Reset everything, all rotations, positions and scales of all bones.
onJointRotPosScaleReset();
//BD - Clear posing when we're done now that we've safely endangered getting spaghetified.
gAgentAvatarp->clearPosing();
gAgentAvatarp->stopMotion(ANIM_BD_POSING_MOTION);
}
//BD - Wipe the joint list.
onJointRefresh();
}
bool BDFloaterPoseCreator::onPoseExport()
{
BDPosingMotion* motion = (BDPosingMotion*)gAgentAvatarp->findMotion(ANIM_BD_POSING_MOTION);
if (!motion)
return false;
if (!gDragonAnimator.mPoseCreatorMotion)
return false;
LLPose* pose = motion->getPose();
if (!pose)
return false;
std::string motion_name = getChild<LLUICtrl>("export_name")->getValue().asString();
if (motion_name.empty())
return false;
std::string full_path = gDirUtilp->getExpandedFilename(LL_PATH_ANIMATIONS, motion_name + ".anim");
return gDragonAnimator.mPoseCreatorMotion->dumpToFile(full_path);
LLKeyframeMotion* temp_motion = NULL;
LLAssetID mMotionID;
LLTransactionID mTransactionID;
//BD - To make this work we'll first need a unique UUID for this animation.
mTransactionID.generate();
mMotionID = mTransactionID.makeAssetID(gAgent.getSecureSessionID());
temp_motion = (LLKeyframeMotion*)gAgentAvatarp->createMotion(mMotionID);
if (!temp_motion)
return false;
LLKeyframeMotion::JointMotionList* joint_motion_list = new LLKeyframeMotion::JointMotionList;
std::vector<LLPointer<LLJointState>> joint_states;
if (joint_motion_list == NULL)
return false;
if (!joint_motion_list->mJointMotionArray.empty())
joint_motion_list->mJointMotionArray.clear();
joint_motion_list->mJointMotionArray.reserve(134);
joint_states.clear();
joint_states.reserve(134);
joint_motion_list->mBasePriority = (LLJoint::JointPriority)getChild<LLUICtrl>("base_priority")->getValue().asInteger();
joint_motion_list->mDuration = 1.f;
joint_motion_list->mEaseInDuration = getChild<LLUICtrl>("ease_in")->getValue().asReal();
joint_motion_list->mEaseOutDuration = getChild<LLUICtrl>("ease_out")->getValue().asReal();
joint_motion_list->mEmoteName = "";
joint_motion_list->mHandPose = (LLHandMotion::eHandPose)getChild<LLUICtrl>("hand_poses")->getValue().asInteger();
joint_motion_list->mLoop = true;
joint_motion_list->mLoopInPoint = 0.0f;
joint_motion_list->mLoopOutPoint = 1.0f;
joint_motion_list->mMaxPriority = LLJoint::ADDITIVE_PRIORITY;
for (S32 i = 0; i <= ATTACHMENT_BONES; i++)
{
for (auto item : mJointScrolls[i]->getAllData())
{
LLJoint* joint = (LLJoint*)item->getUserdata();
if (joint)
{
//BD - Skip this bone if its not enabled. Allows us to selectively include bones.
LLPointer<LLJointState> state = pose->findJointState(joint);
if (!state)
continue;
//BD - Create a new joint motion and add it to the pile.
LLKeyframeMotion::JointMotion* joint_motion = new LLKeyframeMotion::JointMotion;
joint_motion_list->mJointMotionArray.push_back(joint_motion);
//BD - Fill out joint motion with relevant basic data.
joint_motion->mJointName = joint->getName();
joint_motion->mPriority = LLJoint::HIGHEST_PRIORITY;
//BD - Create the basic joint state for this joint and add it to the joint states.
LLPointer<LLJointState> joint_state = new LLJointState;
joint_states.push_back(joint_state);
joint_state->setJoint(joint); // note: can accept NULL
joint_state->setUsage(0);
//BD - Start with filling general rotation data in.
joint_motion->mRotationCurve.mNumKeys = 1;
joint_motion->mRotationCurve.mInterpolationType = LLKeyframeMotion::IT_LINEAR;
//BD - Create a rotation key and put it into the rotation curve.
LLKeyframeMotion::RotationKey rotation_key = LLKeyframeMotion::RotationKey(1.f, joint->getTargetRotation());
joint_motion->mRotationCurve.mKeys[0] = rotation_key;
joint_state->setUsage(joint_state->getUsage() | LLJointState::ROT);
//BD - Fill general position data in.
joint_motion->mPositionCurve.mNumKeys = 1;
joint_motion->mPositionCurve.mInterpolationType = LLKeyframeMotion::IT_LINEAR;
//BD - Create a position key and put it into the position curve.
LLKeyframeMotion::PositionKey position_key = LLKeyframeMotion::PositionKey(1.f, joint->getTargetPosition());
joint_motion->mPositionCurve.mKeys[0] = position_key;
joint_state->setUsage(joint_state->getUsage() | LLJointState::POS);
temp_motion->addJointState(joint_state);
//BD - Start with filling general scale data in.
joint_motion->mScaleCurve.mNumKeys = 1;
joint_motion->mScaleCurve.mInterpolationType = LLKeyframeMotion::IT_LINEAR;
//BD - Create a scale key and put it into the scale curve.
LLKeyframeMotion::ScaleKey scale_key = LLKeyframeMotion::ScaleKey(1.f, joint->getScale());
joint_motion->mScaleCurve.mKeys[0] = scale_key;
joint_motion->mUsage = joint_state->getUsage();
//BD - We do not use constraints so we just leave them out here.
// Should we ever add them we'd do so here.
//joint_motion_list->mConstraints.push_front(constraint);
//BD - Get the pelvis's bounding box and add it.
if (joint->getJointNum() == 0)
{
joint_motion_list->mPelvisBBox.addPoint(position_key.mValue);
}
}
}
}
temp_motion->setJointMotionList(joint_motion_list);
return temp_motion->dumpToFile(full_path);
}
void BDFloaterPoseCreator::onPoseImport()
{
BDPosingMotion* motion = (BDPosingMotion*)gAgentAvatarp->findMotion(ANIM_BD_POSING_MOTION);
if (!motion)
return;
//BD - Put us back in T pose before importing a new animation. We want to apply
// the changes to a clean pose.
onJointRotPosScaleReset();
//BD - Create a new motion from an anim file from disk.
// Let us pick the file we want to preview with the filepicker.
LLFilePicker& picker = LLFilePicker::instance();
if (picker.getOpenFile(LLFilePicker::FFLOAD_ANIM))
{
std::string outfilename = picker.getFirstFile().c_str();
gDragonAnimator.mPoseCreatorMotion = onReadyTempMotion(gDirUtilp->getBaseFileName(outfilename), true);
onJointRefresh();
onPoseReapply();
onKeyframesRebuild();
//onKeyframeRefresh();
//BD - Always select the last entry whenever we switch bones to allow quickly making
// changes or adding new keyframes.
//mKeyframeScroll->selectNthItem(mKeyframeScroll->getItemCount() - 1);
}
}
////////////////////////////////
//BD - Joints
////////////////////////////////
void BDFloaterPoseCreator::onJointRefresh()
{
//BD - Getting collision volumes and attachment points.
std::vector<std::string> cv_names, attach_names;
gAgentAvatarp->getSortedJointNames(1, cv_names);
gAgentAvatarp->getSortedJointNames(2, attach_names);
bool is_posing = gAgentAvatarp->getPosing();
mJointScrolls[JOINTS]->clearRows();
mJointScrolls[COLLISION_VOLUMES]->clearRows();
mJointScrolls[ATTACHMENT_BONES]->clearRows();
LLVector3 rot;
LLVector3 pos;
LLVector3 scale;
LLJoint* joint;
for (S32 i = 0; (joint = gAgentAvatarp->getCharacterJoint(i)); ++i)
{
//BD - Nothing? Invalid? Skip, when we hit the end we'll break out anyway.
if (!joint) continue;
LLSD row;
const std::string joint_name = joint->getName();
//BD - Show some categories to make it a bit easier finding out which
// bone belongs where and what they might be for those who can't use
// bone names.
if (joint->mJointNum == 0 || //mPelvis
joint->mJointNum == 8 || //mHead
joint->mJointNum == 58 || //mCollarLeft
joint->mJointNum == 77 || //mCollarRight
joint->mJointNum == 96 || //mWingsRoot
joint->mJointNum == 107 || //mHipRight
joint->mJointNum == 112 || //mHipLeft
joint->mJointNum == 117 || //mTail1
joint->mJointNum == 123) //mGroin
{
row["columns"][COL_ICON]["column"] = "icon";
row["columns"][COL_ICON]["type"] = "icon";
row["columns"][COL_ICON]["value"] = getString("icon_category");
row["columns"][COL_NAME]["column"] = "joint";
row["columns"][COL_NAME]["value"] = getString("title_" + joint_name);
LLScrollListItem* element = mJointScrolls[JOINTS]->addElement(row);
element->setEnabled(FALSE);
}
row["columns"][COL_ICON]["column"] = "icon";
row["columns"][COL_ICON]["type"] = "icon";
row["columns"][COL_ICON]["value"] = getString("icon_bone");
row["columns"][COL_NAME]["column"] = "joint";
row["columns"][COL_NAME]["value"] = joint_name;
if (is_posing)
{
//BD - Bone Rotations
joint->getTargetRotation().getEulerAngles(&rot.mV[VX], &rot.mV[VY], &rot.mV[VZ]);
row["columns"][COL_ROT_X]["column"] = "x";
row["columns"][COL_ROT_X]["value"] = ll_round(rot.mV[VX], 0.001f);
row["columns"][COL_ROT_Y]["column"] = "y";
row["columns"][COL_ROT_Y]["value"] = ll_round(rot.mV[VY], 0.001f);
row["columns"][COL_ROT_Z]["column"] = "z";
row["columns"][COL_ROT_Z]["value"] = ll_round(rot.mV[VZ], 0.001f);
//BD - Bone Positions
pos = joint->getTargetPosition();
row["columns"][COL_POS_X]["column"] = "pos_x";
row["columns"][COL_POS_X]["value"] = ll_round(pos.mV[VX], 0.001f);
row["columns"][COL_POS_Y]["column"] = "pos_y";
row["columns"][COL_POS_Y]["value"] = ll_round(pos.mV[VY], 0.001f);
row["columns"][COL_POS_Z]["column"] = "pos_z";
row["columns"][COL_POS_Z]["value"] = ll_round(pos.mV[VZ], 0.001f);
//BD - Bone Scales
scale = joint->getScale();
row["columns"][COL_SCALE_X]["column"] = "scale_x";
row["columns"][COL_SCALE_X]["value"] = ll_round(scale.mV[VX], 0.001f);
row["columns"][COL_SCALE_Y]["column"] = "scale_y";
row["columns"][COL_SCALE_Y]["value"] = ll_round(scale.mV[VY], 0.001f);
row["columns"][COL_SCALE_Z]["column"] = "scale_z";
row["columns"][COL_SCALE_Z]["value"] = ll_round(scale.mV[VZ], 0.001f);
}
LLScrollListItem* item = mJointScrolls[JOINTS]->addElement(row);
item->setUserdata(joint);
//BD - We need to check if we are posing or not, simply set all bones to deactivated
// when we are not posed otherwise they will remain on "enabled" state. This behavior
// could be confusing to the user, this is due to how animations work.
if (is_posing)
{
BDPosingMotion* motion = (BDPosingMotion*)gAgentAvatarp->findMotion(ANIM_BD_POSING_MOTION);
if (motion)
{
LLPose* pose = motion->getPose();
if (pose)
{
// BD - We do check here for the joint_state because otherwise we end up with the toggle
// state not appearing properly toggled/untoggled when we first refresh after firing
// up the poser. At the same time this is used to refresh all bone states when we
// load a pose.
LLPointer<LLJointState> joint_state = pose->findJointState(joint);
if (joint_state)
{
((LLScrollListText*)item->getColumn(COL_NAME))->setFontStyle(LLFontGL::BOLD);
}
}
}
}
else
{
((LLScrollListText*)item->getColumn(COL_NAME))->setFontStyle(LLFontGL::NORMAL);
}
}
//BD - We add an empty line into both lists and include an icon to automatically resize
// the list row heights to sync up with the height in our joint list. We remove it
// immediately after anyway.
LLSD test_row;
test_row["columns"][COL_ICON]["column"] = "icon";
test_row["columns"][COL_ICON]["type"] = "icon";
test_row["columns"][COL_ICON]["value"] = getString("icon_category");
mJointScrolls[COLLISION_VOLUMES]->addElement(test_row);
mJointScrolls[COLLISION_VOLUMES]->deleteSingleItem(0);
mJointScrolls[ATTACHMENT_BONES]->addElement(test_row);
mJointScrolls[ATTACHMENT_BONES]->deleteSingleItem(0);
//BD - Collision Volumes
for (auto name : cv_names)
{
joint = gAgentAvatarp->getJoint(name);
//BD - Nothing? Invalid? Skip, when we hit the end we'll break out anyway.
if (!joint) continue;
LLSD row;
row["columns"][COL_ICON]["column"] = "icon";
row["columns"][COL_ICON]["type"] = "icon";
row["columns"][COL_ICON]["value"] = getString("icon_bone");
row["columns"][COL_NAME]["column"] = "joint";
row["columns"][COL_NAME]["value"] = name;
if (is_posing)
{
//BD - Bone Rotations
// It's stupid but we have to define the empty columns here and fill them with
// nothing otherwise the addRow() function is assuming that the sometimes missing
// rotation columns have an "empty" name and thus creates faulty extra columns.
row["columns"][COL_ROT_X]["column"] = "x";
row["columns"][COL_ROT_X]["value"] = "";
row["columns"][COL_ROT_Y]["column"] = "y";
row["columns"][COL_ROT_Y]["value"] = "";
row["columns"][COL_ROT_Z]["column"] = "z";
row["columns"][COL_ROT_Z]["value"] = "";
//BD - Bone Positions
pos = joint->getPosition();
row["columns"][COL_POS_X]["column"] = "pos_x";
row["columns"][COL_POS_X]["value"] = ll_round(pos.mV[VX], 0.001f);
row["columns"][COL_POS_Y]["column"] = "pos_y";
row["columns"][COL_POS_Y]["value"] = ll_round(pos.mV[VY], 0.001f);
row["columns"][COL_POS_Z]["column"] = "pos_z";
row["columns"][COL_POS_Z]["value"] = ll_round(pos.mV[VZ], 0.001f);
//BD - Bone Scales
scale = joint->getScale();
row["columns"][COL_SCALE_X]["column"] = "scale_x";
row["columns"][COL_SCALE_X]["value"] = ll_round(scale.mV[VX], 0.001f);
row["columns"][COL_SCALE_Y]["column"] = "scale_y";
row["columns"][COL_SCALE_Y]["value"] = ll_round(scale.mV[VY], 0.001f);
row["columns"][COL_SCALE_Z]["column"] = "scale_z";
row["columns"][COL_SCALE_Z]["value"] = ll_round(scale.mV[VZ], 0.001f);
}
LLScrollListItem* item = mJointScrolls[COLLISION_VOLUMES]->addElement(row);
item->setUserdata(joint);
}
//BD - Attachment Bones
for (auto name : attach_names)
{
joint = gAgentAvatarp->getJoint(name);
//BD - Nothing? Invalid? Skip, when we hit the end we'll break out anyway.
if (!joint) continue;
LLSD row;
row["columns"][COL_ICON]["column"] = "icon";
row["columns"][COL_ICON]["type"] = "icon";
row["columns"][COL_ICON]["value"] = getString("icon_bone");
row["columns"][COL_NAME]["column"] = "joint";
row["columns"][COL_NAME]["value"] = name;
if (is_posing)
{
//BD - Bone Rotations
// It's stupid but we have to define the empty columns here and fill them with
// nothing otherwise the addRow() function is assuming that the sometimes missing
// rotation columns have an "empty" name and thus creates faulty extra columns.
row["columns"][COL_ROT_X]["column"] = "x";
row["columns"][COL_ROT_X]["value"] = "";
row["columns"][COL_ROT_Y]["column"] = "y";
row["columns"][COL_ROT_Y]["value"] = "";
row["columns"][COL_ROT_Z]["column"] = "z";
row["columns"][COL_ROT_Z]["value"] = "";
//BD - Bone Positions
pos = joint->getPosition();
row["columns"][COL_POS_X]["column"] = "pos_x";
row["columns"][COL_POS_X]["value"] = ll_round(pos.mV[VX], 0.001f);
row["columns"][COL_POS_Y]["column"] = "pos_y";
row["columns"][COL_POS_Y]["value"] = ll_round(pos.mV[VY], 0.001f);
row["columns"][COL_POS_Z]["column"] = "pos_z";
row["columns"][COL_POS_Z]["value"] = ll_round(pos.mV[VZ], 0.001f);
//BD - Bone Scales
scale = joint->getScale();
row["columns"][COL_SCALE_X]["column"] = "scale_x";
row["columns"][COL_SCALE_X]["value"] = ll_round(scale.mV[VX], 0.001f);
row["columns"][COL_SCALE_Y]["column"] = "scale_y";
row["columns"][COL_SCALE_Y]["value"] = ll_round(scale.mV[VY], 0.001f);
row["columns"][COL_SCALE_Z]["column"] = "scale_z";
row["columns"][COL_SCALE_Z]["value"] = ll_round(scale.mV[VZ], 0.001f);
}
LLScrollListItem* item = mJointScrolls[ATTACHMENT_BONES]->addElement(row);
item->setUserdata(joint);
}
onJointControlsRefresh();
}
void BDFloaterPoseCreator::onJointControlsRefresh()
{
bool is_pelvis = false;
bool is_posing = (gAgentAvatarp->isFullyLoaded() && gAgentAvatarp->getPosing());
bool is_previewing = false;
S32 index = mJointTabs->getCurrentPanelIndex();
LLScrollListItem* item = mJointScrolls[index]->getFirstSelected();
is_previewing = gDragonAnimator.mPoseCreatorMotion && !gDragonAnimator.mPoseCreatorMotion->isStopped();
if (item)
{
LLJoint* joint = (LLJoint*)item->getUserdata();
if (joint)
{
if (index == 0)
{
mRotationSliders[VX]->setValue(item->getColumn(COL_ROT_X)->getValue());
mRotationSliders[VY]->setValue(item->getColumn(COL_ROT_Y)->getValue());
mRotationSliders[VZ]->setValue(item->getColumn(COL_ROT_Z)->getValue());
}
is_pelvis = (joint->mJointNum == 0);
mPositionSliders[VX]->setValue(item->getColumn(COL_POS_X)->getValue());
mPositionSliders[VY]->setValue(item->getColumn(COL_POS_Y)->getValue());
mPositionSliders[VZ]->setValue(item->getColumn(COL_POS_Z)->getValue());
//BD - Bone Scales
mScaleSliders[VX]->setValue(item->getColumn(COL_SCALE_X)->getValue());
mScaleSliders[VY]->setValue(item->getColumn(COL_SCALE_Y)->getValue());
mScaleSliders[VZ]->setValue(item->getColumn(COL_SCALE_Z)->getValue());
BDPosingMotion* motion = (BDPosingMotion*)gAgentAvatarp->findMotion(ANIM_BD_POSING_MOTION);
if (motion)
{
//BD - If we don't use our default spherical interpolation, set it once.
motion->setInterpolationTime(0.25f);
motion->setInterpolationType(2);
LLPose* pose = motion->getPose();
if (pose)
{
LLPointer<LLJointState> joint_state = pose->findJointState(joint);
getChild<LLButton>("toggle_bone")->setValue(joint_state.notNull());
}
}
}
}
getChild<LLButton>("toggle_bone")->setEnabled(item && is_posing && index == JOINTS);
mStartPosingBtn->setValue(is_posing);
getChild<LLUICtrl>("export_poses")->setEnabled(is_posing);
getChild<LLUICtrl>("import_poses")->setEnabled(is_posing);
getChild<LLUICtrl>("joints_tabs")->setEnabled(is_posing);
//BD - Enable position tabs whenever positions are available, scales are always enabled
// unless we are editing attachment bones, rotations on the other hand are only
// enabled when editing joints.
S32 curr_idx = mModifierTabs->getCurrentPanelIndex();
mModifierTabs->setEnabled(item && is_posing && !is_previewing);
mModifierTabs->enableTabButton(0, (item && is_posing && index == JOINTS));
mModifierTabs->enableTabButton(1, (item && is_posing));
mModifierTabs->enableTabButton(2, (item && is_posing && index == COLLISION_VOLUMES));
//BD - Swap out of "Position" tab when it's not available.
if (curr_idx == 1 && !mModifierTabs->getTabButtonEnabled(1))
{
mModifierTabs->selectTab(0);
}
//BD - Swap out of "Scale" and "Rotation" tabs when they are not available.
if (curr_idx == 2 && !mModifierTabs->getTabButtonEnabled(2)
|| curr_idx == 0 && !mModifierTabs->getTabButtonEnabled(0))
{
mModifierTabs->selectTab(1);
}
F32 max_val = is_pelvis ? 20.f : 1.0f;
mPositionSliders[VX]->setMaxValue(max_val);
mPositionSliders[VY]->setMaxValue(max_val);
mPositionSliders[VZ]->setMaxValue(max_val);
mPositionSliders[VX]->setMinValue(-max_val);
mPositionSliders[VY]->setMinValue(-max_val);
mPositionSliders[VZ]->setMinValue(-max_val);
//BD - Change our animator's target, make sure it is always up-to-date.
gDragonAnimator.mTargetAvatar = gAgentAvatarp;
getChild<LLUICtrl>("add_keyframe")->setEnabled(is_posing && !is_previewing);
getChild<LLUICtrl>("remove_keyframe")->setEnabled(is_posing && !is_previewing);
getChild<LLUICtrl>("interpolation")->setEnabled(is_posing && !is_previewing);
getChild<LLUICtrl>("key_time")->setEnabled(is_posing && !is_previewing);
mStartPosingBtn->setEnabled(!is_previewing);
}
void BDFloaterPoseCreator::onJointSet(LLUICtrl* ctrl, const LLSD& param)
{
//BD - Rotations are only supported by joints so far, everything
// else snaps back instantly.
LLScrollListItem* item = mJointScrolls[JOINTS]->getFirstSelected();
if (!item)
return;
LLJoint* joint = (LLJoint*)item->getUserdata();
if (!joint)
return;
if (!gDragonAnimator.mPoseCreatorMotion)
return;
LLScrollListItem* key_item = mKeyframeScroll->getFirstSelected();
//BD - Neat yet quick and direct way of rotating our bones.
// No more need to include bone rotation orders.
F32 val = ctrl->getValue().asReal();
S32 axis = param.asInteger();
LLScrollListCell* cell[3] = { item->getColumn(COL_ROT_X), item->getColumn(COL_ROT_Y), item->getColumn(COL_ROT_Z) };
LLQuaternion rot_quat = joint->getTargetRotation();
LLMatrix3 rot_mat;
F32 old_value = cell[axis]->getValue().asReal();
F32 new_value = val - old_value;
LLVector3 vec3;
S32 time = key_item ? key_item->getColumn(0)->getValue().asInteger() : 1;
cell[axis]->setValue(ll_round(val, 0.001f));
vec3.mV[axis] = new_value;
rot_mat = LLMatrix3(vec3.mV[VX], vec3.mV[VY], vec3.mV[VZ]);
rot_quat = LLQuaternion(rot_mat)*rot_quat;
joint->setTargetRotation(rot_quat);
if (!mEasyRotations)
{
rot_quat.getEulerAngles(&vec3.mV[VX], &vec3.mV[VY], &vec3.mV[VZ]);
S32 i = 0;
while (i < 3)
{
if (i != axis)
{
cell[i]->setValue(ll_round(vec3.mV[i], 0.001f));
mRotationSliders[i]->setValue(item->getColumn(i + 2)->getValue());
}
++i;
}
}
//BD - After a lot of different approaches this seemed to be the most feasible and functional.
LLKeyframeMotion::JointMotionList* joint_list = gDragonAnimator.mPoseCreatorMotion->getJointMotionList();
LLKeyframeMotion::JointMotion* joint_motion = joint_list->getJointMotion(joint->getJointNum());
LLKeyframeMotion::RotationCurve rot_curve = joint_motion->mRotationCurve;
//BD - Attempt to find the keyframe first.
bool found = false;
S32 si = mKeyframeScroll->getFirstSelectedIndex() + 1;
for (auto it = joint_motion->mRotationCurve.mKeys.begin();
it != joint_motion->mRotationCurve.mKeys.end(); )
{
auto curr_it = it;
//BD - Previously we were rounding the keyframe time and comparing it to the time set
// in the keyframe list entry, this was ugly and had a big downside, having two
// or more entries with the same time resulting in all of them getting changed.
// Lucky for us, adding new keyframes into any transformation curve automatically
// counts a float value (used here as S32 since its whole numbers anyway) up by 1
// for every keyframe added, this allows us to easily "compare" the what seems to be
// the index number of the keyframe, against the index number in the list.
// This should work for now until we decide to allow reordering keyframes.
//if (ll_round(curr_it->second.mTime, 0.1f) == ll_round(time, 0.1f))
if ((S32)curr_it->first == si)
{
found = true;
curr_it->second.mValue = joint->getTargetRotation();
F32 roll, pitch, yaw;
LLQuaternion rot_quat = joint->getTargetRotation();
rot_quat.getEulerAngles(&roll, &pitch, &yaw);
//BD - Should we really be able to get here? The comparison above should already
// prevent ever getting here since a missing selection means we will never
// find a keyframe. I don't trust anything.
if (key_item)
{
key_item->getColumn(2)->setValue(ll_round(roll, 0.001f));
key_item->getColumn(3)->setValue(ll_round(pitch, 0.001f));
key_item->getColumn(4)->setValue(ll_round(yaw, 0.001f));
}
break;
}
++it;
}
//BD - We couldn't find a keyframe, create one automatically.
if (!found)
{
onKeyframeAdd((time * FRAMETIME), joint);
//BD - Refresh the keyframes.
onKeyframeRefresh();
//BD - Always select the last entry whenever we switch bones to allow quickly making
// changes or adding new keyframes.
mKeyframeScroll->selectNthItem(mKeyframeScroll->getChildCount() - 1);
}
//BD - If we are in Mirror mode, try to find the opposite bone of our currently
// selected one, for now this simply means we take the name and replace "Left"
// with "Right" and vise versa since all bones are conveniently that way.
// TODO: Do this when creating the joint list so we don't try to find the joint
// over and over again.
if (mMirrorMode)
{
LLJoint* mirror_joint = nullptr;
std::string mirror_joint_name = joint->getName();
S32 idx = joint->getName().find("Left");
if (idx != -1)
mirror_joint_name.replace(idx, mirror_joint_name.length(), "Right");
idx = joint->getName().find("Right");
if (idx != -1)
mirror_joint_name.replace(idx, mirror_joint_name.length(), "Left");
if (mirror_joint_name != joint->getName())
{
mirror_joint = gDragonAnimator.mTargetAvatar->mRoot->findJoint(mirror_joint_name);
}
if (mirror_joint)
{
//BD - For the opposite joint we invert X and Z axis, everything else is directly applied
// exactly like we do it in our currently selected joint.
if (axis != 1)
val = -val;
LLQuaternion inv_quat = LLQuaternion(-rot_quat.mQ[VX], rot_quat.mQ[VY], -rot_quat.mQ[VZ], rot_quat.mQ[VW]);
mirror_joint->setTargetRotation(inv_quat);
LLKeyframeMotion::JointMotion* mirror_joint_motion = joint_list->getJointMotion(mirror_joint->getJointNum());
LLKeyframeMotion::RotationCurve mirror_rot_curve = joint_motion->mRotationCurve;
//BD - Attempt to find the keyframe first.
bool found = false;
LLScrollListItem* key_item = mKeyframeScroll->getItemByLabel(mirror_joint_name);
if (key_item)
{
for (auto it = mirror_joint_motion->mRotationCurve.mKeys.begin();
it != mirror_joint_motion->mRotationCurve.mKeys.end(); )
{
auto curr_it = it;
//BD - Previously we were rounding the keyframe time and comparing it to the time set
// in the keyframe list entry, this was ugly and had a big downside, having two
// or more entries with the same time resulting in all of them getting changed.
// Lucky for us, adding new keyframes into any transformation curve automatically
// counts a float value (used here as S32 since its whole numbers anyway) up by 1
// for every keyframe added, this allows us to easily "compare" the what seems to be
// the index number of the keyframe, against the index number in the list.
// This should work for now until we decide to allow reordering keyframes.
//if (ll_round(curr_it->second.mTime, 0.1f) == ll_round(time, 0.1f))
if ((S32)curr_it->first == si)
{
found = true;
curr_it->second.mValue = mirror_joint->getTargetRotation();
F32 roll, pitch, yaw;
LLQuaternion rot_quat = mirror_joint->getTargetRotation();
rot_quat.getEulerAngles(&roll, &pitch, &yaw);
//BD - Should we really be able to get here? The comparison above should already
// prevent ever getting here since a missing selection means we will never
// find a keyframe. I don't trust anything.
if (key_item)
{
key_item->getColumn(2)->setValue(ll_round(roll, 0.001f));
key_item->getColumn(3)->setValue(ll_round(pitch, 0.001f));
key_item->getColumn(4)->setValue(ll_round(yaw, 0.001f));
}
break;
}
++it;
}
}
//BD - We couldn't find a keyframe, create one automatically.
if (!found)
{
onKeyframeAdd((time * FRAMETIME), mirror_joint);
//BD - Refresh the keyframes.
onKeyframeRefresh();
//BD - Always select the last entry whenever we switch bones to allow quickly making
// changes or adding new keyframes.
//mKeyframeScroll->selectNthItem(mKeyframeScroll->getChildCount() - 1);
}
}
}
}
void BDFloaterPoseCreator::onJointPosSet(LLUICtrl* ctrl, const LLSD& param)
{
S32 index = mJointTabs->getCurrentPanelIndex();
LLScrollListItem* item = mJointScrolls[index]->getFirstSelected();
if (!item)
return;
LLJoint* joint = (LLJoint*)item->getUserdata();
if (!joint)
return;
if (!gDragonAnimator.mPoseCreatorMotion)
return;
LLScrollListItem* key_item = mKeyframeScroll->getFirstSelected();
//BD - We could just check whether position information is available since only joints
// which can have their position changed will have position information but we
// want this to be a minefield for crashes.
// Bones that can support position
// 0, 9-37, 39-43, 45-59, 77, 97-107, 110, 112, 115, 117-121, 125, 128-129, 132
// as well as all attachment bones and collision volumes.
F32 val = ctrl->getValue().asReal();
LLScrollListCell* cell[3] = { item->getColumn(COL_POS_X), item->getColumn(COL_POS_Y), item->getColumn(COL_POS_Z) };
LLVector3 vec3 = { F32(cell[VX]->getValue().asReal()),
F32(cell[VY]->getValue().asReal()),
F32(cell[VZ]->getValue().asReal()) };
S32 time = key_item ? key_item->getColumn(0)->getValue().asInteger() : 1;
S32 dir = param.asInteger();
vec3.mV[dir] = val;
cell[dir]->setValue(ll_round(vec3.mV[dir], 0.001f));
joint->setTargetPosition(vec3);
LLKeyframeMotion::JointMotionList* joint_list = gDragonAnimator.mPoseCreatorMotion->getJointMotionList();
LLKeyframeMotion::JointMotion* joint_motion = joint_list->getJointMotion(joint->getJointNum());
LLKeyframeMotion::RotationCurve rot_curve = joint_motion->mRotationCurve;
bool found = false;
S32 si = mKeyframeScroll->getFirstSelectedIndex() + 1;
for (auto it = joint_motion->mPositionCurve.mKeys.begin();
it != joint_motion->mPositionCurve.mKeys.end(); )
{
auto curr_it = it;
//BD - Previously we were rounding the keyframe time and comparing it to the time set
// in the keyframe list entry, this was ugly and had a big downside, having two
// or more entries with the same time resulting in all of them getting changed.
// Lucky for us, adding new keyframes into any transformation curve automatically
// counts a float value (used here as S32 since its whole numbers anyway) up by 1
// for every keyframe added, this allows us to easily "compare" the what seems to be
// the index number of the keyframe, against the index number in the list.
// This should work for now until we decide to allow reordering keyframes.
if ((S32)curr_it->first == si)
{
found = true;
curr_it->second.mValue = joint->getTargetPosition();
LLVector3 pos = joint->getTargetPosition();
//BD - Should we really be able to get here? The comparison above should already
// prevent ever getting here since a missing selection means we will never
// find a keyframe. I don't trust anything.
if (key_item)
{
key_item->getColumn(2)->setValue(ll_round(pos.mV[VX], 0.001f));
key_item->getColumn(3)->setValue(ll_round(pos.mV[VY], 0.001f));
key_item->getColumn(4)->setValue(ll_round(pos.mV[VZ], 0.001f));
}
}
++it;
}
//BD - We couldn't find a keyframe, create one automatically.
if (!found)
{
onKeyframeAdd((time * FRAMETIME), joint);
//BD - Refresh the keyframes.
onKeyframeRefresh();
//BD - Always select the last entry whenever we switch bones to allow quickly making
// changes or adding new keyframes.
mKeyframeScroll->selectNthItem(mKeyframeScroll->getChildCount() - 1);
}
}
void BDFloaterPoseCreator::onJointScaleSet(LLUICtrl* ctrl, const LLSD& param)
{
S32 index = mJointTabs->getCurrentPanelIndex();
LLScrollListItem* item = mJointScrolls[index]->getFirstSelected();
if (!item)
return;
LLJoint* joint = (LLJoint*)item->getUserdata();
if (!joint)
return;
if (!gDragonAnimator.mPoseCreatorMotion)
return;
LLScrollListItem* key_item = mKeyframeScroll->getFirstSelected();
if (!key_item)
return;
F32 val = ctrl->getValue().asReal();
LLScrollListCell* cell[3] = { item->getColumn(COL_SCALE_X), item->getColumn(COL_SCALE_Y), item->getColumn(COL_SCALE_Z) };
LLVector3 vec3 = { F32(cell[VX]->getValue().asReal()),
F32(cell[VY]->getValue().asReal()),
F32(cell[VZ]->getValue().asReal()) };
S32 dir = param.asInteger();
vec3.mV[dir] = val;
cell[dir]->setValue(ll_round(vec3.mV[dir], 0.001f));
joint->setScale(vec3);
LLKeyframeMotion::JointMotionList* joint_list = gDragonAnimator.mPoseCreatorMotion->getJointMotionList();
LLKeyframeMotion::JointMotion* joint_motion = joint_list->getJointMotion(joint->getJointNum());
LLKeyframeMotion::RotationCurve rot_curve = joint_motion->mRotationCurve;
S32 si = mKeyframeScroll->getFirstSelectedIndex() + 1;
for (auto it = joint_motion->mScaleCurve.mKeys.begin();
it != joint_motion->mScaleCurve.mKeys.end(); )
{
auto curr_it = it;
//BD - Previously we were rounding the keyframe time and comparing it to the time set
// in the keyframe list entry, this was ugly and had a big downside, having two
// or more entries with the same time resulting in all of them getting changed.
// Lucky for us, adding new keyframes into any transformation curve automatically
// counts a float value (used here as S32 since its whole numbers anyway) up by 1
// for every keyframe added, this allows us to easily "compare" the what seems to be
// the index number of the keyframe, against the index number in the list.
// This should work for now until we decide to allow reordering keyframes.
if ((S32)curr_it->first == si)
{
curr_it->second.mValue = joint->getScale();
LLVector3 scale = joint->getScale();
key_item->getColumn(2)->setValue(ll_round(scale.mV[VX], 0.001f));
key_item->getColumn(3)->setValue(ll_round(scale.mV[VY], 0.001f));
key_item->getColumn(4)->setValue(ll_round(scale.mV[VZ], 0.001f));
}
++it;
}
}
void BDFloaterPoseCreator::onJointChangeState()
{
BDPosingMotion* motion = (BDPosingMotion*)gAgentAvatarp->findMotion(ANIM_BD_POSING_MOTION);
if (motion)
{
for (auto item : mJointScrolls[JOINTS]->getAllSelected())
{
LLJoint* joint = (LLJoint*)item->getUserdata();
if (joint)
{
LLPose* pose = motion->getPose();
if (pose)
{
LLPointer<LLJointState> joint_state = pose->findJointState(joint);
if (joint_state)
{
motion->removeJointState(joint_state);
((LLScrollListText*)item->getColumn(COL_NAME))->setFontStyle(LLFontGL::NORMAL);
}
else
{
motion->addJointToState(joint);
((LLScrollListText*)item->getColumn(COL_NAME))->setFontStyle(LLFontGL::BOLD);
}
}
}
}
}
onJointControlsRefresh();
}
//BD - We use this to reset everything at once.
void BDFloaterPoseCreator::onJointRotPosScaleReset()
{
//BD - While editing rotations, make sure we use a bit of spherical linear interpolation
// to make movements smoother.
BDPosingMotion* motion = (BDPosingMotion*)gAgentAvatarp->findMotion(ANIM_BD_POSING_MOTION);
if (motion)
{
//BD - If we don't use our default spherical interpolation, set it once.
motion->setInterpolationTime(0.25f);
motion->setInterpolationType(2);
}
for (S32 it = 0; it < 3; ++it)
{
//BD - We use this bool to determine whether or not we'll be in need for a full skeleton
// reset and to prevent checking for it every single time.
for (auto item : mJointScrolls[it]->getAllData())
{
if (item)
{
LLJoint* joint = (LLJoint*)item->getUserdata();
if (joint)
{
//BD - Resetting rotations first if there are any.
if (it == JOINTS)
{
LLQuaternion quat;
LLScrollListCell* col_rot_x = item->getColumn(COL_ROT_X);
LLScrollListCell* col_rot_y = item->getColumn(COL_ROT_Y);
LLScrollListCell* col_rot_z = item->getColumn(COL_ROT_Z);
col_rot_x->setValue(0.000f);
col_rot_y->setValue(0.000f);
col_rot_z->setValue(0.000f);
quat.setEulerAngles(0, 0, 0);
joint->setTargetRotation(quat);
//BD - If we are in Mirror mode, try to find the opposite bone of our currently
// selected one, for now this simply means we take the name and replace "Left"
// with "Right" and vise versa since all bones are conveniently that way.
// TODO: Do this when creating the joint list so we don't try to find the joint
// over and over again.
if (mMirrorMode)
{
LLJoint* mirror_joint = nullptr;
std::string mirror_joint_name = joint->getName();
S32 idx = joint->getName().find("Left");
if (idx != -1)
mirror_joint_name.replace(idx, mirror_joint_name.length(), "Right");
idx = joint->getName().find("Right");
if (idx != -1)
mirror_joint_name.replace(idx, mirror_joint_name.length(), "Left");
if (mirror_joint_name != joint->getName())
{
mirror_joint = gDragonAnimator.mTargetAvatar->mRoot->findJoint(mirror_joint_name);
}
if (mirror_joint)
{
//BD - We also need to find the opposite joint's list entry and change its values to reflect
// the new ones, doing this here is still better than causing a complete refresh.
LLScrollListItem* item2 = mJointScrolls[JOINTS]->getItemByLabel(mirror_joint_name, FALSE, COL_NAME);
if (item2)
{
col_rot_x = item2->getColumn(COL_ROT_X);
col_rot_y = item2->getColumn(COL_ROT_Y);
col_rot_z = item2->getColumn(COL_ROT_Z);
col_rot_x->setValue(0.000f);
col_rot_y->setValue(0.000f);
col_rot_z->setValue(0.000f);
mirror_joint->setTargetRotation(quat);
}
}
}
}
//BD - Resetting positions next.
LLScrollListCell* col_pos_x = item->getColumn(COL_POS_X);
LLScrollListCell* col_pos_y = item->getColumn(COL_POS_Y);
LLScrollListCell* col_pos_z = item->getColumn(COL_POS_Z);
LLVector3 pos = mDefaultPositions[joint->getName()];
col_pos_x->setValue(ll_round(pos.mV[VX], 0.001f));
col_pos_y->setValue(ll_round(pos.mV[VY], 0.001f));
col_pos_z->setValue(ll_round(pos.mV[VZ], 0.001f));
joint->setTargetPosition(pos);
//BD - Resetting scales last.
LLScrollListCell* col_scale_x = item->getColumn(COL_SCALE_X);
LLScrollListCell* col_scale_y = item->getColumn(COL_SCALE_Y);
LLScrollListCell* col_scale_z = item->getColumn(COL_SCALE_Z);
LLVector3 scale = mDefaultScales[joint->getName()];
col_scale_x->setValue(ll_round(scale.mV[VX], 0.001f));
col_scale_y->setValue(ll_round(scale.mV[VY], 0.001f));
col_scale_z->setValue(ll_round(scale.mV[VZ], 0.001f));
joint->setScale(scale);
}
}
}
}
onJointControlsRefresh();
}
//BD - Used to reset rotations only.
void BDFloaterPoseCreator::onJointRotationReset()
{
//BD - While editing rotations, make sure we use a bit of spherical linear interpolation
// to make movements smoother.
BDPosingMotion* motion = (BDPosingMotion*)gAgentAvatarp->findMotion(ANIM_BD_POSING_MOTION);
if (motion)
{
//BD - If we don't use our default spherical interpolation, set it once.
motion->setInterpolationTime(0.25f);
motion->setInterpolationType(2);
}
for (auto item : mJointScrolls[JOINTS]->getAllSelected())
{
if (item)
{
LLJoint* joint = (LLJoint*)item->getUserdata();
if (joint)
{
LLQuaternion quat;
LLScrollListCell* col_x = item->getColumn(COL_ROT_X);
LLScrollListCell* col_y = item->getColumn(COL_ROT_Y);
LLScrollListCell* col_z = item->getColumn(COL_ROT_Z);
col_x->setValue(0.000f);
col_y->setValue(0.000f);
col_z->setValue(0.000f);
quat.setEulerAngles(0, 0, 0);
joint->setTargetRotation(quat);
//BD - If we are in Mirror mode, try to find the opposite bone of our currently
// selected one, for now this simply means we take the name and replace "Left"
// with "Right" and vise versa since all bones are conveniently that way.
// TODO: Do this when creating the joint list so we don't try to find the joint
// over and over again.
if (mMirrorMode)
{
LLJoint* mirror_joint = nullptr;
std::string mirror_joint_name = joint->getName();
S32 idx = joint->getName().find("Left");
if (idx != -1)
mirror_joint_name.replace(idx, mirror_joint_name.length(), "Right");
idx = joint->getName().find("Right");
if (idx != -1)
mirror_joint_name.replace(idx, mirror_joint_name.length(), "Left");
if (mirror_joint_name != joint->getName())
{
mirror_joint = gDragonAnimator.mTargetAvatar->mRoot->findJoint(mirror_joint_name);
}
if (mirror_joint)
{
//BD - We also need to find the opposite joint's list entry and change its values to reflect
// the new ones, doing this here is still better than causing a complete refresh.
LLScrollListItem* item2 = mJointScrolls[JOINTS]->getItemByLabel(mirror_joint_name, FALSE, COL_NAME);
if (item2)
{
col_x = item2->getColumn(COL_ROT_X);
col_y = item2->getColumn(COL_ROT_Y);
col_z = item2->getColumn(COL_ROT_Z);
col_x->setValue(0.000f);
col_y->setValue(0.000f);
col_z->setValue(0.000f);
mirror_joint->setTargetRotation(quat);
}
}
}
}
}
}
onJointControlsRefresh();
}
//BD - Used to reset positions, this is very tricky hence why it was separated.
// It causes the avatar to flinch for a second which doesn't look as nice as resetting
// rotations does.
void BDFloaterPoseCreator::onJointPositionReset()
{
S32 index = mJointTabs->getCurrentPanelIndex();
//BD - When resetting positions, we don't use interpolation for now, it looks stupid.
BDPosingMotion* motion = (BDPosingMotion*)gAgentAvatarp->findMotion(ANIM_BD_POSING_MOTION);
if (motion)
{
motion->setInterpolationTime(0.25f);
motion->setInterpolationType(2);
}
//BD - We use this bool to prevent going through attachment override reset every single time.
//bool has_reset = false;
for (auto item : mJointScrolls[index]->getAllSelected())
{
if (item)
{
LLJoint* joint = (LLJoint*)item->getUserdata();
if (joint)
{
LLScrollListCell* col_pos_x = item->getColumn(COL_POS_X);
LLScrollListCell* col_pos_y = item->getColumn(COL_POS_Y);
LLScrollListCell* col_pos_z = item->getColumn(COL_POS_Z);
LLVector3 pos = mDefaultPositions[joint->getName()];
col_pos_x->setValue(ll_round(pos.mV[VX], 0.001f));
col_pos_y->setValue(ll_round(pos.mV[VY], 0.001f));
col_pos_z->setValue(ll_round(pos.mV[VZ], 0.001f));
joint->setTargetPosition(pos);
}
}
}
onJointControlsRefresh();
}
//BD - Used to reset scales only.
void BDFloaterPoseCreator::onJointScaleReset()
{
S32 index = mJointTabs->getCurrentPanelIndex();
//BD - Clear all attachment bone scale changes we've done, they are not automatically
// reverted.
for (auto item : mJointScrolls[index]->getAllSelected())
{
if (item)
{
LLJoint* joint = (LLJoint*)item->getUserdata();
if (joint)
{
LLScrollListCell* col_scale_x = item->getColumn(COL_SCALE_X);
LLScrollListCell* col_scale_y = item->getColumn(COL_SCALE_Y);
LLScrollListCell* col_scale_z = item->getColumn(COL_SCALE_Z);
LLVector3 scale = mDefaultScales[joint->getName()];
col_scale_x->setValue(ll_round(scale.mV[VX], 0.001f));
col_scale_y->setValue(ll_round(scale.mV[VY], 0.001f));
col_scale_z->setValue(ll_round(scale.mV[VZ], 0.001f));
joint->setScale(scale);
}
}
}
onJointControlsRefresh();
}
//BD - Used to revert rotations only.
void BDFloaterPoseCreator::onJointRotationRevert()
{
//BD - While editing rotations, make sure we use a bit of spherical linear interpolation
// to make movements smoother.
BDPosingMotion* motion = (BDPosingMotion*)gAgentAvatarp->findMotion(ANIM_BD_POSING_MOTION);
if (motion)
{
//BD - If we don't use our default spherical interpolation, set it once.
motion->setInterpolationTime(0.25f);
motion->setInterpolationType(2);
}
for (auto item : mJointScrolls[JOINTS]->getAllSelected())
{
if (item)
{
LLJoint* joint = (LLJoint*)item->getUserdata();
if (joint)
{
//BD - Reverting rotations first if there are any.
LLQuaternion quat = mDefaultRotations[joint->getName()];
LLVector3 rot;
quat.getEulerAngles(&rot.mV[VX], &rot.mV[VY], &rot.mV[VZ]);
LLScrollListCell* col_rot_x = item->getColumn(COL_ROT_X);
LLScrollListCell* col_rot_y = item->getColumn(COL_ROT_Y);
LLScrollListCell* col_rot_z = item->getColumn(COL_ROT_Z);
col_rot_x->setValue(rot.mV[VX]);
col_rot_y->setValue(rot.mV[VY]);
col_rot_z->setValue(rot.mV[VZ]);
joint->setTargetRotation(quat);
//BD - If we are in Mirror mode, try to find the opposite bone of our currently
// selected one, for now this simply means we take the name and replace "Left"
// with "Right" and vise versa since all bones are conveniently that way.
// TODO: Do this when creating the joint list so we don't try to find the joint
// over and over again.
if (mMirrorMode)
{
LLJoint* mirror_joint = nullptr;
std::string mirror_joint_name = joint->getName();
S32 idx = joint->getName().find("Left");
if (idx != -1)
mirror_joint_name.replace(idx, mirror_joint_name.length(), "Right");
idx = joint->getName().find("Right");
if (idx != -1)
mirror_joint_name.replace(idx, mirror_joint_name.length(), "Left");
if (mirror_joint_name != joint->getName())
{
mirror_joint = gDragonAnimator.mTargetAvatar->mRoot->findJoint(mirror_joint_name);
}
if (mirror_joint)
{
//BD - We also need to find the opposite joint's list entry and change its values to reflect
// the new ones, doing this here is still better than causing a complete refresh.
LLScrollListItem* item2 = mJointScrolls[JOINTS]->getItemByLabel(mirror_joint_name, FALSE, COL_NAME);
if (item2)
{
col_rot_x = item2->getColumn(COL_ROT_X);
col_rot_y = item2->getColumn(COL_ROT_Y);
col_rot_z = item2->getColumn(COL_ROT_Z);
col_rot_x->setValue(rot.mV[VX]);
col_rot_y->setValue(rot.mV[VY]);
col_rot_z->setValue(rot.mV[VZ]);
mirror_joint->setTargetRotation(quat);
}
}
}
}
}
}
onJointControlsRefresh();
}
//BD - Flip our pose (mirror it)
void BDFloaterPoseCreator::onFlipPose()
{
LLVOAvatar* avatar = gDragonAnimator.mTargetAvatar;
if (!avatar || avatar->isDead()) return;
if (!(avatar->getRegion() == gAgent.getRegion())) return;
LLJoint* joint = nullptr;
bool flipped[134] = { false };
for (S32 i = 0; (joint = avatar->getCharacterJoint(i)); ++i)
{
//BD - Skip if we already flipped this bone.
if (flipped[i]) continue;
//BD - Nothing? Invalid? Skip, when we hit the end we'll break out anyway.
if (!joint) continue;
LLVector3 rot, mirror_rot;
LLQuaternion rot_quat, mirror_rot_quat;
std::string joint_name = joint->getName();
std::string mirror_joint_name = joint->getName();
//BD - Attempt to find the "right" version of this bone first, we assume we always
// end up with the "left" version of a bone first.
S32 idx = joint->getName().find("Left");
if (idx != -1)
mirror_joint_name.replace(idx, mirror_joint_name.length(), "Right");
//BD - Attempt to find the "right" version of this bone first, this is necessary
// because there are a couple bones starting with the "right" bone.
idx = joint->getName().find("Right");
if (idx != -1)
mirror_joint_name.replace(idx, mirror_joint_name.length(), "Left");
LLJoint* mirror_joint = nullptr;
if (mirror_joint_name != joint->getName())
mirror_joint = gDragonAnimator.mTargetAvatar->mRoot->findJoint(mirror_joint_name);
//BD - Collect the joint and mirror joint entries and their cells, we need them later.
LLScrollListItem* item1 = mJointScrolls[JOINTS]->getItemByLabel(joint_name, FALSE, COL_NAME);
LLScrollListItem* item2 = nullptr;
//BD - Get the rotation of our current bone and that of the mirror bone (if available).
// Flip our current bone's rotation and apply it to the mirror bone (if available).
// Flip the mirror bone's rotation (if available) and apply it to our current bone.
// If the mirror bone does not exist, flip the current bone rotation and use that.
rot_quat = joint->getTargetRotation();
LLQuaternion inv_rot_quat = LLQuaternion(-rot_quat.mQ[VX], rot_quat.mQ[VY], -rot_quat.mQ[VZ], rot_quat.mQ[VW]);
inv_rot_quat.getEulerAngles(&rot[VX], &rot[VY], &rot[VZ]);
if (mirror_joint)
{
mirror_rot_quat = mirror_joint->getTargetRotation();
LLQuaternion inv_mirror_rot_quat = LLQuaternion(-mirror_rot_quat.mQ[VX], mirror_rot_quat.mQ[VY], -mirror_rot_quat.mQ[VZ], mirror_rot_quat.mQ[VW]);
inv_mirror_rot_quat.getEulerAngles(&mirror_rot[VX], &mirror_rot[VY], &mirror_rot[VZ]);
mirror_joint->setTargetRotation(inv_rot_quat);
joint->setTargetRotation(inv_mirror_rot_quat);
item2 = mJointScrolls[JOINTS]->getItemByLabel(mirror_joint_name, FALSE, COL_NAME);
//BD - Make sure we flag this bone as flipped so we skip it next time we iterate over it.
flipped[mirror_joint->getJointNum()] = true;
}
else
{
joint->setTargetRotation(inv_rot_quat);
}
S32 axis = 0;
while (axis <= 2)
{
//BD - Now flip the list entry values.
if (item1)
{
if (mirror_joint)
item1->getColumn(axis + 2)->setValue(ll_round(mirror_rot[axis], 0.001f));
else
item1->getColumn(axis + 2)->setValue(ll_round(rot[axis], 0.001f));
}
//BD - Now flip the mirror joint list entry values.
if (item2)
item2->getColumn(axis + 2)->setValue(ll_round(rot[axis], 0.001f));
++axis;
}
flipped[i] = true;
}
}
////////////////////////////////
//BD - Utility Functions
////////////////////////////////
//BD - Poser Utility Functions
void BDFloaterPoseCreator::onJointPasteRotation()
{
for (auto item : mJointScrolls[JOINTS]->getAllSelected())
{
LLJoint* joint = (LLJoint*)item->getUserdata();
if (joint)
{
LLScrollListCell* col_rot_x = item->getColumn(COL_ROT_X);
LLScrollListCell* col_rot_y = item->getColumn(COL_ROT_Y);
LLScrollListCell* col_rot_z = item->getColumn(COL_ROT_Z);
LLVector3 euler_rot;
LLQuaternion rot = (LLQuaternion)mClipboard["rot"];
joint->setTargetRotation(rot);
rot.getEulerAngles(&euler_rot.mV[VX], &euler_rot.mV[VY], &euler_rot.mV[VZ]);
col_rot_x->setValue(ll_round(euler_rot.mV[VX], 0.001f));
col_rot_y->setValue(ll_round(euler_rot.mV[VY], 0.001f));
col_rot_z->setValue(ll_round(euler_rot.mV[VZ], 0.001f));
}
}
}
void BDFloaterPoseCreator::onJointPastePosition()
{
for (auto item : mJointScrolls[JOINTS]->getAllSelected())
{
LLJoint* joint = (LLJoint*)item->getUserdata();
if (joint)
{
LLScrollListCell* col_pos_x = item->getColumn(COL_POS_X);
LLScrollListCell* col_pos_y = item->getColumn(COL_POS_Y);
LLScrollListCell* col_pos_z = item->getColumn(COL_POS_Z);
LLVector3 pos = (LLVector3)mClipboard["pos"];
joint->setTargetPosition(pos);
col_pos_x->setValue(ll_round(pos.mV[VX], 0.001f));
col_pos_y->setValue(ll_round(pos.mV[VY], 0.001f));
col_pos_z->setValue(ll_round(pos.mV[VZ], 0.001f));
}
}
}
void BDFloaterPoseCreator::onJointPasteScale()
{
for (auto item : mJointScrolls[JOINTS]->getAllSelected())
{
LLJoint* joint = (LLJoint*)item->getUserdata();
if (joint)
{
LLScrollListCell* col_scale_x = item->getColumn(COL_SCALE_X);
LLScrollListCell* col_scale_y = item->getColumn(COL_SCALE_Y);
LLScrollListCell* col_scale_z = item->getColumn(COL_SCALE_Z);
LLVector3 scale = (LLVector3)mClipboard["scale"];
joint->setScale(scale);
col_scale_x->setValue(ll_round(scale.mV[VX], 0.001f));
col_scale_y->setValue(ll_round(scale.mV[VY], 0.001f));
col_scale_z->setValue(ll_round(scale.mV[VZ], 0.001f));
}
}
}
void BDFloaterPoseCreator::onJointMirror()
{
for (auto item : mJointScrolls[JOINTS]->getAllSelected())
{
LLJoint* joint = (LLJoint*)item->getUserdata();
if (joint)
{
LLVector3 euler_rot;
LLQuaternion rot_quat = joint->getTargetRotation();
//BD - Simply mirror the current bone's rotation like we'd do if we pressed the mirror
// button without a mirror bone available.
LLQuaternion inv_rot_quat = LLQuaternion(-rot_quat.mQ[VX], rot_quat.mQ[VY], -rot_quat.mQ[VZ], rot_quat.mQ[VW]);
inv_rot_quat.getEulerAngles(&euler_rot[VX], &euler_rot[VY], &euler_rot[VZ]);
joint->setTargetRotation(inv_rot_quat);
LLScrollListCell* col_rot_x = item->getColumn(COL_ROT_X);
LLScrollListCell* col_rot_y = item->getColumn(COL_ROT_Y);
LLScrollListCell* col_rot_z = item->getColumn(COL_ROT_Z);
col_rot_x->setValue(ll_round(euler_rot.mV[VX], 0.001f));
col_rot_y->setValue(ll_round(euler_rot.mV[VY], 0.001f));
col_rot_z->setValue(ll_round(euler_rot.mV[VZ], 0.001f));
}
}
}
void BDFloaterPoseCreator::onJointSymmetrize()
{
for (auto item : mJointScrolls[JOINTS]->getAllSelected())
{
LLJoint* joint = (LLJoint*)item->getUserdata();
if (joint)
{
std::string joint_name = joint->getName();
std::string mirror_joint_name = joint->getName();
//BD - Attempt to find the "right" version of this bone, if we can't find it try
// the left version.
S32 idx = joint->getName().find("Left");
if (idx != -1)
mirror_joint_name.replace(idx, mirror_joint_name.length(), "Right");
idx = joint->getName().find("Right");
if (idx != -1)
mirror_joint_name.replace(idx, mirror_joint_name.length(), "Left");
LLJoint* mirror_joint = nullptr;
if (mirror_joint_name != joint->getName())
mirror_joint = gDragonAnimator.mTargetAvatar->mRoot->findJoint(mirror_joint_name);
//BD - Get the rotation of the mirror bone (if available).
// Flip the mirror bone's rotation (if available) and apply it to our current bone.
if (mirror_joint)
{
LLVector3 mirror_rot;
LLQuaternion mirror_rot_quat;
mirror_rot_quat = mirror_joint->getTargetRotation();
LLQuaternion inv_mirror_rot_quat = LLQuaternion(-mirror_rot_quat.mQ[VX], mirror_rot_quat.mQ[VY], -mirror_rot_quat.mQ[VZ], mirror_rot_quat.mQ[VW]);
inv_mirror_rot_quat.getEulerAngles(&mirror_rot[VX], &mirror_rot[VY], &mirror_rot[VZ]);
joint->setTargetRotation(inv_mirror_rot_quat);
LLScrollListCell* col_rot_x = item->getColumn(COL_ROT_X);
LLScrollListCell* col_rot_y = item->getColumn(COL_ROT_Y);
LLScrollListCell* col_rot_z = item->getColumn(COL_ROT_Z);
col_rot_x->setValue(ll_round(mirror_rot.mV[VX], 0.001f));
col_rot_y->setValue(ll_round(mirror_rot.mV[VY], 0.001f));
col_rot_z->setValue(ll_round(mirror_rot.mV[VZ], 0.001f));
}
}
}
}
void BDFloaterPoseCreator::onJointCopyTransforms()
{
LLScrollListItem* item = mJointScrolls[JOINTS]->getFirstSelected();
LLJoint* joint = (LLJoint*)item->getUserdata();
if (joint)
{
mClipboard["rot"] = joint->getTargetRotation().getValue();
mClipboard["pos"] = joint->getTargetPosition().getValue();
mClipboard["scale"] = joint->getScale().getValue();
LL_INFOS("Posing") << "Copied all transforms " << LL_ENDL;
}
}
bool BDFloaterPoseCreator::onJointContextMenuEnable(const LLSD& param)
{
std::string action = param.asString();
if (action == "clipboard")
{
return mClipboard.has("rot");
}
if (action == "enable_bone")
{
LLScrollListItem* item = mJointScrolls[JOINTS]->getFirstSelected();
if (item)
{
LLJoint* joint = (LLJoint*)item->getUserdata();
if (joint)
{
BDPosingMotion* motion = (BDPosingMotion*)gAgentAvatarp->findMotion(ANIM_BD_POSING_MOTION);
LLPose* pose = motion->getPose();
if (pose)
{
LLPointer<LLJointState> joint_state = pose->findJointState(joint);
return joint_state;
}
}
}
}
return false;
}
void BDFloaterPoseCreator::onJointContextMenuAction(const LLSD& param)
{
std::string action = param.asString();
if (action == "copy_transforms")
{
onJointCopyTransforms();
}
else if (action == "paste_rot")
{
onJointPasteRotation();
}
else if (action == "paste_pos")
{
onJointPastePosition();
}
else if (action == "paste_scale")
{
onJointPasteScale();
}
else if (action == "paste_rot_pos")
{
onJointPasteRotation();
onJointPastePosition();
}
else if (action == "paste_rot_scale")
{
onJointPasteRotation();
onJointPasteScale();
}
else if (action == "paste_pos_scale")
{
onJointPastePosition();
onJointPasteScale();
}
else if (action == "paste_all")
{
onJointPasteRotation();
onJointPastePosition();
onJointPasteScale();
}
else if (action == "symmetrize")
{
onJointSymmetrize();
}
else if (action == "mirror")
{
onJointMirror();
}
else if (action == "enable_bone")
{
onJointChangeState();
}
else if (action == "enable_override")
{
}
else if (action == "enable_offset")
{
}
else if (action == "reset_rot")
{
onJointRotationReset();
}
else if (action == "reset_pos")
{
onJointPositionReset();
}
else if (action == "reset_scale")
{
onJointScaleReset();
}
else if (action == "reset_all")
{
//BD - We do all 3 here because the combined function resets all bones regardless of
// our selection, these only reset the selected ones.
onJointRotationReset();
onJointPositionReset();
onJointScaleReset();
}
}
////////////////////////////////
//BD - Misc Functions
////////////////////////////////
LLKeyframeMotion* BDFloaterPoseCreator::onReadyTempMotion(std::string filename, bool eternal)
{
std::string outfilename = gDirUtilp->getExpandedFilename(LL_PATH_ANIMATIONS, filename);
S32 file_size;
BOOL success = FALSE;
LLAPRFile infile;
LLKeyframeMotion* mTempMotion = NULL;
LLAssetID mMotionID;
LLTransactionID mTransactionID;
//BD - To make this work we'll first need a unique UUID for this animation.
mTransactionID.generate();
mMotionID = mTransactionID.makeAssetID(gAgent.getSecureSessionID());
mTempMotion = (LLKeyframeMotion*)gAgentAvatarp->createMotion(mMotionID);
LL_INFOS("Posing") << mMotionID << LL_ENDL;
//BD - Find and open the file, we'll need to write it temporarily into the VFS pool.
infile.open(outfilename, LL_APR_RB, NULL, &file_size);
if (infile.getFileHandle())
{
U8 *anim_data;
S32 anim_file_size;
LLFileSystem file(mMotionID, LLAssetType::AT_ANIMATION, LLFileSystem::WRITE);
const S32 buf_size = 65536;
U8 copy_buf[buf_size];
while ((file_size = infile.read(copy_buf, buf_size)))
{
file.write(copy_buf, file_size);
}
//BD - Now that we wrote the temporary file, find it and use it to set the size
// and buffer into which we will unpack the .anim file into.
LLFileSystem* anim_file = new LLFileSystem(mMotionID, LLAssetType::AT_ANIMATION);
anim_file_size = anim_file->getSize();
anim_data = new U8[anim_file_size];
anim_file->read(anim_data, anim_file_size);
//BD - Cleanup everything we don't need anymore.
delete anim_file;
anim_file = NULL;
//BD - Use the datapacker now to actually deserialize and unpack the animation
// into our temporary motion so we can use it after we added it into the list.
LLDataPackerBinaryBuffer dp(anim_data, anim_file_size);
success = mTempMotion && mTempMotion->deserialize(dp, mMotionID);
if (success && eternal)
{
mTempMotion->setEternal(true);
}
//BD - Cleanup the rest.
delete[]anim_data;
}
infile.close();
//return success ? mMotionID : LLUUID::null;
return mTempMotion;
}
void BDFloaterPoseCreator::onCreateTempMotion()
{
LLAssetID mMotionID;
LLTransactionID mTransactionID;
//BD - To make this work we'll first need a unique UUID for this animation.
mTransactionID.generate();
mMotionID = mTransactionID.makeAssetID(gAgent.getSecureSessionID());
LLKeyframeMotion* temp_motion = (LLKeyframeMotion*)gAgentAvatarp->createMotion(mMotionID);
if (!temp_motion)
return;
LLKeyframeMotion::JointMotionList* mTempMotionList = new LLKeyframeMotion::JointMotionList;
std::vector<LLPointer<LLJointState>> joint_states;
if (!mTempMotionList)
return;
if (!mTempMotionList->mJointMotionArray.empty())
mTempMotionList->mJointMotionArray.clear();
mTempMotionList->mJointMotionArray.reserve(134);
joint_states.clear();
joint_states.reserve(134);
mTempMotionList->mBasePriority = (LLJoint::JointPriority)getChild<LLUICtrl>("base_priority")->getValue().asInteger();
mTempMotionList->mDuration = getChild<LLUICtrl>("keyframe_duration")->getValue().asReal();
mTempMotionList->mEaseInDuration = getChild<LLUICtrl>("ease_in")->getValue().asReal();
mTempMotionList->mEaseOutDuration = getChild<LLUICtrl>("ease_out")->getValue().asReal();
mTempMotionList->mEmoteName = "";
mTempMotionList->mHandPose = (LLHandMotion::eHandPose)getChild<LLUICtrl>("hand_poses")->getValue().asInteger();
mTempMotionList->mLoop = true;
mTempMotionList->mLoopInPoint = 0.0f;
mTempMotionList->mLoopOutPoint = getChild<LLUICtrl>("keyframe_duration")->getValue().asReal();
mTempMotionList->mMaxPriority = LLJoint::HIGHEST_PRIORITY;
for (S32 i = 0; i <= ATTACHMENT_BONES; i++)
{
for (auto item : mJointScrolls[i]->getAllData())
{
LLJoint* joint = (LLJoint*)item->getUserdata();
if (joint)
{
//BD - Create a new joint motion and add it to the pile.
LLKeyframeMotion::JointMotion* joint_motion = new LLKeyframeMotion::JointMotion;
mTempMotionList->mJointMotionArray.push_back(joint_motion);
//BD - Fill out joint motion with relevant basic data.
joint_motion->mJointName = joint->getName();
joint_motion->mPriority = LLJoint::HIGHEST_PRIORITY;
//BD - Create the basic joint state for this joint and add it to the joint states.
LLPointer<LLJointState> joint_state = new LLJointState;
joint_states.push_back(joint_state);
joint_state->setJoint(joint); // note: can accept NULL
joint_state->setUsage(0);
//BD - Start with filling general rotation data in.
joint_motion->mRotationCurve.mNumKeys = 0;
joint_motion->mRotationCurve.mInterpolationType = LLKeyframeMotion::IT_LINEAR;
//BD - Create a rotation key and put it into the rotation curve.
/*LLKeyframeMotion::RotationKey rotation_key = LLKeyframeMotion::RotationKey(1.0f, joint->getTargetRotation());
joint_motion->mRotationCurve.mKeys[0] = rotation_key;
joint_state->setUsage(joint_state->getUsage() | LLJointState::ROT);*/
//BD - Fill general position data in.
joint_motion->mPositionCurve.mNumKeys = 0;
joint_motion->mPositionCurve.mInterpolationType = LLKeyframeMotion::IT_LINEAR;
/*LLKeyframeMotion::PositionKey position_key;
if (joint->mHasPosition)
{
//BD - Create a position key and put it into the position curve.
LLKeyframeMotion::PositionKey position_key = LLKeyframeMotion::PositionKey(1.f, joint->getTargetPosition());
joint_motion->mPositionCurve.mKeys[0] = position_key;
joint_state->setUsage(joint_state->getUsage() | LLJointState::POS);
}*/
temp_motion->addJointState(joint_state);
//BD - Start with filling general scale data in.
joint_motion->mScaleCurve.mNumKeys = 0;
joint_motion->mScaleCurve.mInterpolationType = LLKeyframeMotion::IT_LINEAR;
//BD - Create a scale key and put it into the scale curve.
/*LLKeyframeMotion::ScaleKey scale_key = LLKeyframeMotion::ScaleKey(1.f, joint->getScale());
joint_motion->mScaleCurve.mKeys[0] = scale_key;*/
joint_motion->mUsage = joint_state->getUsage();
//BD - We do not use constraints so we just leave them out here.
// Should we ever add them we'd do so here.
//joint_motion_list->mConstraints.push_front(constraint);
//BD - Get the pelvis's bounding box and add it.
if (joint->getJointNum() == 0)
{
//mTempMotionList->mPelvisBBox.addPoint(position_key.mPosition);
mTempMotionList->mPelvisBBox.addPoint(joint->getTargetPosition());
}
}
}
}
temp_motion->setJointMotionList(mTempMotionList);
//BD - We export this file immediately so we can import it later.
// This is going to be the animation we write everything we do into
// and it also neatly acts as an "autosave" thing that we can just
// load back in case we quit out of the pose creator.
// The main reason we do this however is because we are going to import
// this animation locally which will create a fully setup LLKeyframeAnimation
// that doesn't have some weird things going on causing crashes, it is also
// a lot easier to do.
std::string full_path = gDirUtilp->getExpandedFilename(LL_PATH_ANIMATIONS, "_poser_temp.anim");
temp_motion->dumpToFile(full_path);
//return temp_motion;
}
//BD - This is used to collect all default values at the beginning to revert to later on.
void BDFloaterPoseCreator::onCollectDefaults()
{
LLQuaternion rot;
LLVector3 pos;
LLVector3 scale;
LLJoint* joint;
//BD - Getting collision volumes and attachment points.
std::vector<std::string> joint_names, cv_names, attach_names;
gAgentAvatarp->getSortedJointNames(0, joint_names);
gAgentAvatarp->getSortedJointNames(1, cv_names);
gAgentAvatarp->getSortedJointNames(2, attach_names);
mDefaultRotations.clear();
mDefaultScales.clear();
mDefaultPositions.clear();
for (auto name : joint_names)
{
joint = gAgentAvatarp->getJoint(name);
//BD - Nothing? Invalid? Skip, when we hit the end we'll break out anyway.
if (!joint) continue;
LLSD row;
rot = joint->getTargetRotation();
mDefaultRotations.insert(std::pair<std::string, LLQuaternion>(name, rot));
//BD - We always get the values but we don't write them out as they are not relevant for the
// user yet but we need them to establish default values we revert to later on.
scale = joint->getScale();
mDefaultScales.insert(std::pair<std::string, LLVector3>(name, scale));
//BD - We always get the values but we don't write them out as they are not relevant for the
// user yet but we need them to establish default values we revert to later on.
pos = joint->getPosition();
mDefaultPositions.insert(std::pair<std::string, LLVector3>(name, pos));
}
//BD - Collision Volumes
for (auto name : cv_names)
{
LLJoint* joint = gAgentAvatarp->getJoint(name);
//BD - Nothing? Invalid? Skip, when we hit the end we'll break out anyway.
if (!joint) continue;
//BD - We always get the values but we don't write them out as they are not relevant for the
// user yet but we need them to establish default values we revert to later on.
pos = joint->getPosition();
scale = joint->getScale();
mDefaultPositions.insert(std::pair<std::string, LLVector3>(name, pos));
mDefaultScales.insert(std::pair<std::string, LLVector3>(name, scale));
}
//BD - Attachment Bones
for (auto name : attach_names)
{
LLJoint* joint = gAgentAvatarp->getJoint(name);
//BD - Nothing? Invalid? Skip, when we hit the end we'll break out anyway.
if (!joint) continue;
//BD - We always get the values but we don't write them out as they are not relevant for the
// user yet but we need them to establish default values we revert to later on.
pos = joint->getPosition();
scale = joint->getScale();
mDefaultPositions.insert(std::pair<std::string, LLVector3>(name, pos));
mDefaultScales.insert(std::pair<std::string, LLVector3>(name, scale));
}
}