| |
| |
|
|
| #include "tstroke.h" |
| #include "tgl.h" |
| |
| |
| #include "tenv.h" |
| |
| |
| #include "toonz/tstageobjectcmd.h" |
| #include "toonz/toonzimageutils.h" |
| #include "toonz/txshcolumn.h" |
| #include "toonz/txshsimplelevel.h" |
| #include "toonz/tstageobjecttree.h" |
| #include "toonz/tstageobjectspline.h" |
| #include "toonz/toonzscene.h" |
| #include "toonz/stage.h" |
| #include "toonz/txshcell.h" |
| #include "toonz/dpiscale.h" |
| #include "toonz/skeleton.h" |
| #include "toonz/tscenehandle.h" |
| #include "toonz/tobjecthandle.h" |
| #include "toonz/tpinnedrangeset.h" |
| #include "toonz/tframehandle.h" |
| #include "toonz/tcolumnhandle.h" |
| #include "toonz/txsheethandle.h" |
| #include "toonz/stageobjectutil.h" |
| |
| |
| #include "toonzqt/selection.h" |
| #include "toonzqt/selectioncommandids.h" |
| |
| |
| #include "tools/tool.h" |
| #include "tools/cursors.h" |
| #include "tools/toolutils.h" |
| |
| |
| #include <QCoreApplication> // Qt translation support |
| #include <QPainter> |
| #include <QGLWidget> // for QGLWidget::convertToGLFormat |
| #include <QPainterPath> |
| #include <QString> |
| #include <QImage> |
| #include <QFont> |
| #include <QFontMetrics> |
| #include <QKeyEvent> |
| |
| #include "skeletonsubtools.h" |
| #include "skeletontool.h" |
| |
| using namespace SkeletonSubtools; |
| |
| TEnv::IntVar SkeletonGlobalKeyFrame("SkeletonToolGlobalKeyFrame", 0); |
| TEnv::IntVar SkeletonInverseKinematics("SkeletonToolInverseKinematics", 0); |
| |
| #define BUILD_SKELETON L"Build Skeleton" |
| #define ANIMATE L"Animate" |
| #define INVERSE_KINEMATICS L"Inverse Kinematics" |
| |
| const double alpha = 0.4; |
| |
| using SkeletonSubtools::HookData; |
| |
| |
| |
| inline std::string removeTrailingH(std::string handle) { |
| if (handle.find("H") == 0) |
| return handle.substr(1); |
| else |
| return handle; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static bool isAncestorOf(int ancestorIndex, int descendentIndex) { |
| TStageObjectId ancestorId = TStageObjectId::ColumnId(ancestorIndex); |
| TStageObjectId descendentId = TStageObjectId::ColumnId(descendentIndex); |
| TXsheet *xsh = TTool::getApplication()->getCurrentXsheet()->getXsheet(); |
| while (descendentId != ancestorId && descendentId.isColumn()) |
| descendentId = xsh->getStageObjectParent(descendentId); |
| return descendentId == ancestorId; |
| } |
| |
| |
| |
| static void getHooks(std::vector<HookData> &hooks, TXsheet *xsh, int row, |
| int col, TPointD dpiScale) { |
| |
| |
| |
| TXshCell cell = xsh->getCell(row, col); |
| if (!cell.m_level) return; |
| |
| TStageObjectId columnId = TStageObjectId::ColumnId(col); |
| |
| |
| std::string handle = |
| xsh->getStageObject(TStageObjectId::ColumnId(col))->getHandle(); |
| bool handleIsHook = handle.find("H") == 0; |
| TAffine aff = xsh->getPlacement(columnId, row); |
| |
| |
| |
| |
| TAffine imageDpiAff; |
| if (cell.m_level->getSimpleLevel()) |
| imageDpiAff = |
| getDpiAffine(cell.m_level->getSimpleLevel(), cell.m_frameId, true); |
| |
| |
| TPointD center = xsh->getCenter(columnId, row); |
| if (handleIsHook) center = TPointD(0, 0); |
| |
| |
| hooks.push_back(HookData(xsh, col, 0, aff * TScale(Stage::inch) * center)); |
| |
| |
| HookSet *hookSet = cell.m_level->getHookSet(); |
| if (hookSet && hookSet->getHookCount() > 0) { |
| for (int j = 0; j < hookSet->getHookCount(); j++) { |
| Hook *hook = hookSet->getHook(j); |
| if (hook && !hook->isEmpty()) { |
| TPointD pos = hook->getAPos(cell.m_frameId); |
| pos = aff * imageDpiAff * pos; |
| hooks.push_back(HookData(xsh, col, j + 1, pos)); |
| } |
| } |
| } |
| } |
| |
| |
| |
| static void getConnectedColumns(std::set<int> &connectedColumns, TXsheet *xsh, |
| int col) { |
| TStageObjectId id; |
| |
| id = TStageObjectId::ColumnId(col); |
| do { |
| connectedColumns.insert(id.getIndex()); |
| id = xsh->getStageObjectParent(id); |
| } while (id.isColumn()); |
| |
| for (int i = 0; i < xsh->getColumnCount(); i++) { |
| id = TStageObjectId::ColumnId(i); |
| std::vector<TStageObjectId> stack; |
| |
| |
| while (id.isColumn() && connectedColumns.count(id.getIndex()) == 0) { |
| stack.push_back(id); |
| id = xsh->getStageObjectParent(id); |
| } |
| if (id.isColumn()) { |
| |
| for (int j = 0; j < (int)stack.size(); j++) |
| connectedColumns.insert(stack[j].getIndex()); |
| } |
| } |
| } |
| |
| static bool canShowBone(Skeleton::Bone *bone, TXsheet *xsh, int row) { |
| TStageObjectId id = bone->getStageObject()->getId(); |
| if (!xsh->getCell(row, id.getIndex()).isEmpty() && |
| xsh->getColumn(id.getIndex())->isCamstandVisible()) |
| return true; |
| int i; |
| for (i = 0; i < bone->getChildCount(); i++) { |
| if (canShowBone(bone->getChild(i), xsh, row)) return true; |
| } |
| return false; |
| } |
| |
| |
| |
| HookData::HookData(TXsheet *xsh, int columnIndex, int hookId, |
| const TPointD &pos) |
| : m_columnIndex(columnIndex) |
| , m_hookId(hookId) |
| , m_pos(pos) |
| , m_isPivot(false) { |
| std::string handle = |
| xsh->getStageObject(TStageObjectId::ColumnId(columnIndex))->getHandle(); |
| if (m_hookId == 0) { |
| |
| |
| if (handle.find("H") == 0) |
| m_name = "B"; |
| else { |
| m_name = handle; |
| m_isPivot = true; |
| } |
| } else { |
| m_name = std::to_string(m_hookId); |
| m_isPivot = "H" + m_name == handle; |
| } |
| } |
| |
| |
| |
| enum ToolDevice { |
| TD_None = -1, |
| TD_Translation = 1, |
| TD_Rotation, |
| TD_Center, |
| TD_ChangeParent, |
| TD_ChangeDrawing, |
| TD_IncrementDrawing, |
| TD_DecrementDrawing, |
| TD_InverseKinematics, |
| |
| TD_Hook = 10000, |
| TD_LockStageObject = 20000, |
| TD_MagicLink = 30000, |
| |
| TD_Test = 100000 |
| }; |
| |
| |
| |
| |
| |
| |
| |
| SkeletonTool skeletonTool; |
| |
| |
| |
| SkeletonTool::SkeletonTool() |
| : TTool("T_Skeleton") |
| , m_active(false) |
| , m_device(TD_None) |
| , m_mode("Mode:") |
| , m_showOnlyActiveSkeleton("Show Only Active Skeleton", false) |
| , m_globalKeyframes("Global Key", false) |
| , m_dragTool(0) |
| , m_firstTime(true) |
| , m_currentFrame(-1) |
| , m_parentProbe() |
| , m_parentProbeEnabled(false) |
| , m_otherColumn(-1) |
| , m_otherColumnBBoxAff() |
| , m_otherColumnBBox() |
| , m_labelPos(0, 0) |
| , m_label("") { |
| bind(TTool::CommonLevels); |
| m_prop.bind(m_mode); |
| m_prop.bind(m_globalKeyframes); |
| m_prop.bind(m_showOnlyActiveSkeleton); |
| m_mode.setId("SkeletonMode"); |
| m_globalKeyframes.setId("GlobalKey"); |
| m_showOnlyActiveSkeleton.setId("ShowOnlyActiveSkeleton"); |
| |
| m_mode.addValue(BUILD_SKELETON); |
| m_mode.addValue(ANIMATE); |
| m_mode.addValue(INVERSE_KINEMATICS); |
| |
| m_commandHandler = new CommandHandler(); |
| m_commandHandler->setTempPinnedSet(&m_temporaryPinnedColumns); |
| } |
| |
| |
| |
| SkeletonTool::~SkeletonTool() { delete m_dragTool; } |
| |
| |
| |
| bool SkeletonTool::onPropertyChanged(std::string propertyName) { |
| SkeletonGlobalKeyFrame = (int)(m_globalKeyframes.getValue()); |
| |
| invalidate(); |
| return false; |
| } |
| |
| |
| |
| bool SkeletonTool::isGlobalKeyframesEnabled() const { |
| return m_globalKeyframes.getValue(); |
| } |
| |
| |
| |
| void SkeletonTool::updateTranslation() { |
| |
| m_showOnlyActiveSkeleton.setQStringName(tr("Show Only Active Skeleton")); |
| m_globalKeyframes.setQStringName(tr("Global Key")); |
| m_mode.setQStringName(tr("Mode:")); |
| m_mode.setItemUIName(BUILD_SKELETON, tr("Build Skeleton")); |
| m_mode.setItemUIName(ANIMATE, tr("Animate")); |
| m_mode.setItemUIName(INVERSE_KINEMATICS, tr("Inverse Kinematics")); |
| } |
| |
| |
| |
| bool SkeletonTool::doesApply() const { |
| TTool::Application *app = TTool::getApplication(); |
| TXsheet *xsh = app->getCurrentXsheet()->getXsheet(); |
| assert(xsh); |
| TStageObjectId objId = app->getCurrentObject()->getObjectId(); |
| if (objId.isColumn()) { |
| TXshColumn *column = xsh->getColumn(objId.getIndex()); |
| if (column && column->getSoundColumn()) return false; |
| } |
| return true; |
| } |
| |
| |
| |
| void SkeletonTool::mouseMove(const TPointD &, const TMouseEvent &e) { |
| int selectedDevice = pick(e.m_pos); |
| if (selectedDevice != m_device) { |
| m_device = selectedDevice; |
| invalidate(); |
| } |
| } |
| |
| |
| |
| void SkeletonTool::leftButtonDown(const TPointD &ppos, const TMouseEvent &e) { |
| m_otherColumn = -1; |
| m_otherColumnBBox = TRectD(); |
| m_otherColumnBBoxAff = TAffine(); |
| m_labelPos = TPointD(0, 0); |
| m_label = ""; |
| |
| TUndoManager::manager()->beginBlock(); |
| if (!doesApply()) return; |
| |
| assert(m_dragTool == 0); |
| m_dragTool = 0; |
| |
| TTool::Application *app = TTool::getApplication(); |
| int currentColumnIndex = app->getCurrentColumn()->getColumnIndex(); |
| TXsheet *xsh = app->getCurrentScene()->getScene()->getXsheet(); |
| TPointD pos = ppos; |
| |
| int selectedDevice = pick(e.m_pos); |
| |
| |
| if (selectedDevice == TD_ChangeDrawing || |
| selectedDevice == TD_IncrementDrawing || |
| selectedDevice == TD_DecrementDrawing) { |
| int d = 0; |
| if (selectedDevice == TD_IncrementDrawing) |
| d = 1; |
| else if (selectedDevice == TD_DecrementDrawing) |
| d = -1; |
| m_dragTool = new ChangeDrawingTool(this, d); |
| m_dragTool->leftButtonDown(ppos, e); |
| return; |
| } |
| |
| |
| if (TD_Hook <= selectedDevice && selectedDevice < TD_Hook + 50) { |
| TXsheet *xsh = app->getCurrentXsheet()->getXsheet(); |
| TStageObjectId objId = TStageObjectId::ColumnId(currentColumnIndex); |
| TPointD p0 = getCurrentColumnMatrix() * TPointD(0, 0); |
| HookData hook(xsh, currentColumnIndex, selectedDevice - TD_Hook, p0); |
| TStageObjectCmd::setHandle(objId, hook.getHandle(), |
| app->getCurrentXsheet()); |
| app->getCurrentXsheet()->notifyXsheetChanged(); |
| invalidate(); |
| return; |
| } |
| |
| |
| if (TD_MagicLink <= selectedDevice && |
| selectedDevice < TD_MagicLink + (int)m_magicLinks.size()) { |
| magicLink(selectedDevice - TD_MagicLink); |
| app->getCurrentXsheet()->notifyXsheetChanged(); |
| return; |
| } |
| |
| m_device = selectedDevice; |
| bool justSelected = false; |
| |
| if (m_device < 0) { |
| |
| std::vector<int> columnIndexes; |
| getViewer()->posToColumnIndexes(e.m_pos, columnIndexes, 5.0, false); |
| if (!columnIndexes.empty()) { |
| int columnIndex; |
| columnIndex = columnIndexes.back(); |
| |
| if (columnIndex >= 0 && columnIndex != currentColumnIndex) { |
| if (!isColumnLocked(columnIndex)) { |
| pos = getMatrix() * pos; |
| app->getCurrentColumn()->setColumnIndex(columnIndex); |
| updateMatrix(); |
| currentColumnIndex = columnIndex; |
| justSelected = true; |
| pos = getMatrix().inv() * pos; |
| } else { |
| m_label = "Column is locked"; |
| m_labelPos = pos; |
| } |
| } |
| } |
| } |
| |
| if (m_device < 0) { |
| if (m_mode.getValue() == INVERSE_KINEMATICS) |
| m_device = TD_InverseKinematics; |
| else if (m_mode.getValue() == ANIMATE) |
| m_device = TD_Rotation; |
| } |
| |
| |
| if (TD_LockStageObject <= m_device && m_device < TD_LockStageObject + 1000) { |
| int columnIndex = m_device - TD_LockStageObject; |
| int frame = app->getCurrentFrame()->getFrame(); |
| if (e.isCtrlPressed() || e.isShiftPressed()) { |
| |
| |
| togglePinnedStatus(columnIndex, frame, e.isShiftPressed()); |
| invalidate(); |
| m_dragTool = 0; |
| return; |
| } |
| Skeleton *skeleton = new Skeleton(); |
| buildSkeleton(*skeleton, currentColumnIndex); |
| if (skeleton->getBoneByColumnIndex(columnIndex) == |
| skeleton->getRootBone()) { |
| app->getCurrentColumn()->setColumnIndex(columnIndex); |
| m_device = TD_Translation; |
| } else |
| return; |
| } |
| |
| switch (m_device) { |
| case TD_Center: |
| m_dragTool = new DragCenterTool(this); |
| break; |
| case TD_Translation: |
| m_dragTool = new DragPositionTool(this); |
| break; |
| case TD_Rotation: |
| m_dragTool = new DragRotationTool(this, justSelected); |
| break; |
| case TD_ChangeParent: |
| m_dragTool = new ParentChangeTool(this, getViewer()); |
| break; |
| case TD_InverseKinematics: { |
| Skeleton *skeleton = new Skeleton(); |
| buildSkeleton(*skeleton, currentColumnIndex); |
| m_dragTool = new IKTool(this, getViewer(), skeleton, currentColumnIndex); |
| break; |
| } |
| } |
| |
| if (m_dragTool) { |
| m_dragTool->leftButtonDown(pos, e); |
| invalidate(); |
| } |
| } |
| |
| |
| |
| void SkeletonTool::leftButtonDrag(const TPointD &pos, const TMouseEvent &e) { |
| if (m_dragTool) { |
| m_dragTool->leftButtonDrag(pos, e); |
| invalidate(); |
| } |
| } |
| |
| |
| |
| void SkeletonTool::leftButtonUp(const TPointD &pos, const TMouseEvent &e) { |
| m_label = ""; |
| m_labelPos = TPointD(0, 0); |
| |
| if (m_dragTool) { |
| m_dragTool->leftButtonUp(pos, e); |
| delete m_dragTool; |
| m_dragTool = 0; |
| |
| TTool::getApplication()->getCurrentXsheet()->notifyXsheetChanged(); |
| TTool::getApplication()->getCurrentObject()->notifyObjectIdChanged( |
| false); |
| } |
| if (m_device == TD_IncrementDrawing || m_device == TD_DecrementDrawing || |
| m_device == TD_ChangeDrawing) |
| m_device = pick(e.m_pos); |
| else |
| m_device = -1; |
| invalidate(); |
| TUndoManager::manager()->endBlock(); |
| } |
| |
| |
| |
| bool SkeletonTool::keyDown(QKeyEvent *event) { |
| ChangeDrawingTool tool(this, 0); |
| switch (event->key()) { |
| case Qt::Key_Up: |
| tool.changeDrawing(1); |
| break; |
| case Qt::Key_Down: |
| tool.changeDrawing(-1); |
| break; |
| default: |
| return false; |
| break; |
| } |
| invalidate(); |
| return true; |
| } |
| |
| |
| |
| class TogglePinnedStatusUndo final : public TUndo { |
| SkeletonTool *m_tool; |
| std::set<int> m_oldTemp, m_newTemp; |
| int m_columnIndex, m_oldColumnIndex; |
| std::pair<int, int> m_newRange, m_oldRange; |
| TAffine m_oldPlacement, m_newPlacement; |
| std::vector<std::pair<TStageObjectId, TStageObject::Keyframe>> m_keyframes; |
| int m_frame; |
| |
| public: |
| TogglePinnedStatusUndo(SkeletonTool *tool, int frame) |
| : m_tool(tool) |
| , m_oldTemp() |
| , m_newTemp() |
| , m_columnIndex(-1) |
| , m_oldColumnIndex(-1) |
| , m_newRange(0, -1) |
| , m_oldRange(0, -1) |
| , m_frame(frame) {} |
| |
| void addBoneId(const TStageObjectId &id) { |
| TStageObject *stageObject = getXsheet()->getStageObject(id); |
| if (stageObject) { |
| TStageObject::Keyframe k = stageObject->getKeyframe(m_frame); |
| m_keyframes.push_back(std::make_pair(id, k)); |
| } |
| } |
| |
| void setOldRange(int columnIndex, int first, int second, |
| const TAffine &placement) { |
| m_oldColumnIndex = columnIndex; |
| m_oldRange = std::make_pair(first, second); |
| m_oldPlacement = placement; |
| } |
| void setNewRange(int columnIndex, int first, int second, |
| const TAffine &placement) { |
| m_columnIndex = columnIndex; |
| m_newRange = std::make_pair(first, second); |
| m_newPlacement = placement; |
| } |
| void setOldTemp(const std::set<int> &oldTemp) { m_oldTemp = oldTemp; } |
| void setNewTemp(const std::set<int> &newTemp) { m_newTemp = newTemp; } |
| |
| TStageObject *getStageObject(int columnIndex) const { |
| return TTool::getApplication() |
| ->getCurrentXsheet() |
| ->getXsheet() |
| ->getStageObject(TStageObjectId::ColumnId(columnIndex)); |
| } |
| |
| TPinnedRangeSet *getRangeSet(int columnIndex) const { |
| return getStageObject(columnIndex)->getPinnedRangeSet(); |
| } |
| |
| TXsheet *getXsheet() const { |
| return TTool::getApplication()->getCurrentXsheet()->getXsheet(); |
| } |
| |
| void notify() const { |
| m_tool->invalidate(); |
| TXsheet *xsh = getXsheet(); |
| int index = m_columnIndex; |
| if (index < 0) index = m_oldColumnIndex; |
| if (index >= 0) { |
| TStageObjectId id = TStageObjectId::ColumnId(index); |
| TStageObjectId parentId; |
| while (parentId = xsh->getStageObjectParent(id), parentId.isColumn()) |
| id = parentId; |
| xsh->getStageObject(id)->invalidate(); |
| TTool::getApplication()->getCurrentXsheet()->notifyXsheetChanged(); |
| TTool::getApplication()->getCurrentObject()->notifyObjectIdChanged(false); |
| } |
| } |
| |
| void undo() const override { |
| m_tool->setTemporaryPinnedColumns(m_oldTemp); |
| |
| if (m_columnIndex >= 0) |
| getRangeSet(m_columnIndex) |
| ->removeRange(m_newRange.first, m_newRange.second); |
| if (m_oldColumnIndex >= 0) { |
| TPinnedRangeSet *rangeSet = getRangeSet(m_oldColumnIndex); |
| rangeSet->setRange(m_oldRange.first, m_oldRange.second); |
| rangeSet->setPlacement(m_oldPlacement); |
| } |
| TXsheet *xsh = getXsheet(); |
| for (int i = 0; i < (int)m_keyframes.size(); i++) { |
| TStageObject *stageObject = |
| getXsheet()->getStageObject(m_keyframes[i].first); |
| if (!stageObject) continue; |
| stageObject->removeKeyframeWithoutUndo(m_frame); |
| if (m_keyframes[i].second.m_isKeyframe) |
| stageObject->setKeyframeWithoutUndo(m_frame, m_keyframes[i].second); |
| } |
| |
| notify(); |
| } |
| |
| void redo() const override { |
| TXsheet *xsh = getXsheet(); |
| for (int i = 0; i < (int)m_keyframes.size(); i++) { |
| TStageObject *stageObject = |
| getXsheet()->getStageObject(m_keyframes[i].first); |
| if (stageObject) stageObject->setKeyframeWithoutUndo(m_frame); |
| } |
| m_tool->setTemporaryPinnedColumns(m_newTemp); |
| |
| if (m_oldColumnIndex >= 0) |
| getRangeSet(m_oldColumnIndex) |
| ->removeRange(m_oldRange.first, m_oldRange.second); |
| if (m_columnIndex >= 0) { |
| TPinnedRangeSet *rangeSet = getRangeSet(m_columnIndex); |
| rangeSet->setRange(m_newRange.first, m_newRange.second); |
| rangeSet->setPlacement(m_newPlacement); |
| } |
| notify(); |
| } |
| int getSize() const override { return sizeof *this; } |
| }; |
| |
| |
| |
| void SkeletonTool::togglePinnedStatus(int columnIndex, int frame, |
| bool shiftPressed) { |
| Skeleton skeleton; |
| buildSkeleton(skeleton, columnIndex); |
| if (!skeleton.getRootBone() || !skeleton.getRootBone()->getStageObject()) |
| return; |
| Skeleton::Bone *bone = skeleton.getBoneByColumnIndex(columnIndex); |
| assert(bone); |
| if (!bone) return; |
| |
| TogglePinnedStatusUndo *undo = new TogglePinnedStatusUndo(this, frame); |
| for (int i = 0; i < skeleton.getBoneCount(); i++) { |
| TStageObject *obj = skeleton.getBone(i)->getStageObject(); |
| if (obj) { |
| undo->addBoneId(obj->getId()); |
| obj->setKeyframeWithoutUndo(frame); |
| } |
| } |
| |
| getApplication()->getCurrentXsheet()->notifyXsheetChanged(); |
| getApplication()->getCurrentObject()->notifyObjectIdChanged(false); |
| |
| undo->setOldTemp(m_temporaryPinnedColumns); |
| bool isTemporaryPinned = m_temporaryPinnedColumns.count(columnIndex) > 0; |
| if (shiftPressed || isTemporaryPinned) { |
| if (isTemporaryPinned) |
| m_temporaryPinnedColumns.erase(columnIndex); |
| else |
| m_temporaryPinnedColumns.insert(columnIndex); |
| } else { |
| TXsheet *xsh = TTool::getApplication()->getCurrentXsheet()->getXsheet(); |
| TAffine placement = |
| xsh->getPlacement(bone->getStageObject()->getId(), frame); |
| |
| TStageObjectId rootId = skeleton.getRootBone()->getStageObject()->getId(); |
| TAffine rootPlacement = xsh->getPlacement(rootId, frame); |
| |
| int pinnedStatus = bone->getPinnedStatus(); |
| if (pinnedStatus != Skeleton::Bone::PINNED) { |
| int oldPinned = -1; |
| for (int i = 0; i < skeleton.getBoneCount(); i++) { |
| TStageObject *obj = skeleton.getBone(i)->getStageObject(); |
| if (obj->getPinnedRangeSet()->isPinned(frame)) { |
| oldPinned = i; |
| break; |
| } |
| } |
| |
| int lastFrame = 1000000; |
| if (oldPinned >= 0) { |
| assert(skeleton.getBone(oldPinned) != bone); |
| TStageObject *obj = skeleton.getBone(oldPinned)->getStageObject(); |
| const TPinnedRangeSet::Range *range = |
| obj->getPinnedRangeSet()->getRange(frame); |
| assert(range && range->first <= frame && frame <= range->second); |
| lastFrame = range->second; |
| TPinnedRangeSet *rangeSet = obj->getPinnedRangeSet(); |
| rangeSet->removeRange(frame, range->second); |
| obj->invalidate(); |
| undo->setOldRange(oldPinned, frame, range->second, |
| rangeSet->getPlacement()); |
| } else { |
| for (int i = 0; i < skeleton.getBoneCount(); i++) { |
| TStageObject *obj = skeleton.getBone(i)->getStageObject(); |
| const TPinnedRangeSet::Range *range = |
| obj->getPinnedRangeSet()->getNextRange(frame); |
| if (range) { |
| assert(range->first > frame); |
| if (range->first - 1 < lastFrame) lastFrame = range->first - 1; |
| } |
| } |
| } |
| |
| TStageObject *obj = bone->getStageObject(); |
| TPinnedRangeSet *rangeSet = obj->getPinnedRangeSet(); |
| rangeSet->setRange(frame, lastFrame); |
| if (frame == 0) { |
| |
| |
| |
| TStageObject *rootObj = skeleton.getRootBone()->getStageObject(); |
| rootObj->setStatus(TStageObject::XY); |
| placement = rootObj->getPlacement(0).inv() * placement; |
| rootObj->setStatus(TStageObject::IK); |
| rangeSet->setPlacement(placement); |
| rootObj->invalidate(); |
| } |
| undo->setNewRange(bone->getColumnIndex(), frame, lastFrame, |
| rangeSet->getPlacement()); |
| } |
| } |
| undo->setNewTemp(m_temporaryPinnedColumns); |
| TUndoManager::manager()->add(undo); |
| } |
| |
| |
| |
| void SkeletonTool::drawSkeleton(const Skeleton &skeleton, int row) { |
| bool buildingSkeleton = m_mode.getValue() == BUILD_SKELETON; |
| bool ikEnabled = m_mode.getValue() == INVERSE_KINEMATICS; |
| |
| TXsheet *xsh = getXsheet(); |
| std::vector<int> showBoneIndex; |
| int i; |
| for (i = 0; i < skeleton.getBoneCount(); i++) { |
| Skeleton::Bone *bone = skeleton.getBone(i); |
| TStageObjectId id = bone->getStageObject()->getId(); |
| bool canShow = canShowBone(bone, xsh, row); |
| if (!canShow) continue; |
| showBoneIndex.push_back(i); |
| } |
| |
| bool changingParent = dynamic_cast<ParentChangeTool *>(m_dragTool) != 0; |
| TStageObjectId currentObjectId = |
| TTool::getApplication()->getCurrentObject()->getObjectId(); |
| std::string currentHandle = xsh->getStageObject(currentObjectId)->getHandle(); |
| |
| for (i = 0; i < (int)showBoneIndex.size(); i++) { |
| Skeleton::Bone *bone = skeleton.getBone(showBoneIndex[i]); |
| TStageObjectId id = bone->getStageObject()->getId(); |
| bool isCurrent = id == currentObjectId; |
| if (isCurrent && buildingSkeleton && m_parentProbeEnabled) { |
| if (!m_magicLinks.empty()) { |
| drawBone(bone->getCenter(), m_magicLinks[0].m_h1.m_pos, false); |
| } |
| drawBone(bone->getCenter(), m_parentProbe, true); |
| } else if (ikEnabled) { |
| if (bone->getParent()) |
| drawIKBone(bone->getCenter(), bone->getParent()->getCenter()); |
| } else if (bone->getParent() || isCurrent) { |
| double pixelSize = getPixelSize(); |
| TPointD a = bone->getCenter(); |
| TPointD b, pm; |
| if (bone->getParent()) { |
| b = bone->getParent()->getCenter(); |
| pm = (a + b) * 0.5; |
| } else { |
| pm = b = a + TPointD(0, 60) * pixelSize; |
| } |
| bool boneIsVisible = false; |
| if (buildingSkeleton) { |
| boneIsVisible = true; |
| if (bone->isSelected()) |
| drawBone(a, b, true); |
| else if (!m_showOnlyActiveSkeleton.getValue()) |
| drawBone(a, b, false); |
| else |
| boneIsVisible = false; |
| } |
| if (boneIsVisible && isCurrent) { |
| |
| double r = pixelSize * 5; |
| if (isPicking()) { |
| |
| |
| glPushName(TD_ChangeParent); |
| tglDrawDisk(pm, r); |
| glPopName(); |
| } else { |
| if (m_device == TD_ChangeParent) { |
| glColor4d(0.47 * alpha, 0.6 * alpha, 0.65 * alpha, alpha); |
| r *= 1.5; |
| } else |
| glColor4d(0.37 * alpha, 0.5 * alpha, 0.55 * alpha, alpha); |
| glRectd(pm.x - r, pm.y - r, pm.x + r, pm.y + r); |
| glColor3d(0, 0, 0); |
| tglDrawRect(pm.x - r, pm.y - r, pm.x + r, pm.y + r); |
| } |
| } |
| } |
| } |
| for (i = 0; i < (int)showBoneIndex.size(); i++) { |
| Skeleton::Bone *bone = skeleton.getBone(showBoneIndex[i]); |
| if (!m_showOnlyActiveSkeleton.getValue() || bone->isSelected()) |
| drawJoint(bone->getCenter(), |
| currentObjectId == bone->getStageObject()->getId() && |
| currentHandle.find("H") != 0); |
| } |
| } |
| |
| |
| |
| void SkeletonTool::getImageBoundingBox(TRectD &bbox, TAffine &aff, int frame, |
| int columnIndex) { |
| TAffine columnAff = |
| getXsheet()->getPlacement(TStageObjectId::ColumnId(columnIndex), frame); |
| |
| TXshCell cell = getXsheet()->getCell(frame, columnIndex); |
| TImageP image = cell.getImage(false); |
| TToonzImageP ti = image; |
| TVectorImageP vi = image; |
| if (ti) { |
| TAffine imageDpiAff; |
| if (cell.m_level->getSimpleLevel()) |
| imageDpiAff = |
| getDpiAffine(cell.m_level->getSimpleLevel(), cell.m_frameId, true); |
| aff = columnAff * imageDpiAff; |
| bbox = ToonzImageUtils::convertRasterToWorld(convert(ti->getBBox()), ti) * |
| ti->getSubsampling(); |
| ToolUtils::drawRect(bbox * ti->getSubsampling(), TPixel32(200, 200, 200), |
| 0x5555); |
| } else if (vi) { |
| bbox = vi->getBBox(); |
| aff = columnAff; |
| } else { |
| bbox = TRectD(); |
| aff = TAffine(); |
| } |
| } |
| |
| |
| |
| void SkeletonTool::drawLevelBoundingBox(int frame, int columnIndex) { |
| TAffine affine = getCurrentColumnMatrix(); |
| TXshCell cell = getXsheet()->getCell(frame, columnIndex); |
| TImageP image = cell.getImage(false); |
| TToonzImageP ti = image; |
| TVectorImageP vi = image; |
| glPushMatrix(); |
| if (affine != getMatrix()) tglMultMatrix(getMatrix().inv() * affine); |
| if (ti) { |
| TPointD dpiScale = getViewer()->getDpiScale(); |
| glScaled(dpiScale.x, dpiScale.y, 1); |
| TRectD bbox = |
| ToonzImageUtils::convertRasterToWorld(convert(ti->getBBox()), ti); |
| ToolUtils::drawRect(bbox * ti->getSubsampling(), TPixel32(200, 200, 200), |
| 0x5555); |
| } |
| if (vi) { |
| TRectD bbox = vi->getBBox(); |
| ToolUtils::drawRect(bbox, TPixel32(200, 200, 200), 0x5555); |
| } |
| glPopMatrix(); |
| } |
| |
| |
| |
| void SkeletonTool::drawIKJoint(const Skeleton::Bone *bone) { |
| TPointD pos = bone->getCenter(); |
| const double r0 = 6 * getPixelSize(), r1 = r0 / 3; |
| int code = TD_LockStageObject + bone->getColumnIndex(); |
| glPushName(code); |
| glColor3d(0.8, 0.5, 0.05); |
| if (bone->getPinnedStatus() != Skeleton::Bone::FREE) { |
| if (bone->getPinnedStatus() == Skeleton::Bone::TEMP_PINNED) { |
| double r1 = r0 * 0.60; |
| glColor3d(60.0 / 255.0, 250.0 / 255.0, 1.0); |
| glRectd(pos.x - r0, pos.y - r0, pos.x + r0, pos.y + r0); |
| |
| glColor3d(0.78, 0.62, 0); |
| glRectd(pos.x - r1, pos.y - r1, pos.x + r1, pos.y + r1); |
| |
| glColor3d(0.2, 0.1, 0.05); |
| tglDrawRect(pos.x - r0, pos.y - r0, pos.x + r0, pos.y + r0); |
| tglDrawRect(pos.x - r1, pos.y - r1, pos.x + r1, pos.y + r1); |
| } else { |
| glColor3d(0, 175.0 / 255.0, 1.0); |
| glRectd(pos.x - r0, pos.y - r0, pos.x + r0, pos.y + r0); |
| glColor3d(0.2, 0.1, 0.05); |
| tglDrawRect(pos.x - r0, pos.y - r0, pos.x + r0, pos.y + r0); |
| } |
| } else { |
| if (bone->isSelected()) |
| glColor3d(1, 0.78, 0.19); |
| else |
| glColor3d(0.78, 0.62, 0); |
| tglDrawDisk(pos, r0); |
| glColor3d(0.2, 0.1, 0.05); |
| tglDrawCircle(pos, r0); |
| } |
| |
| if (m_device == code) { |
| glColor3d(0.9, 0.1, 0.1); |
| tglFillRect(pos.x - r1, pos.y - r1, pos.x + r1, pos.y + r1); |
| } else { |
| glColor3d(0.2, 0.1, 0.05); |
| const double r3 = 2 * getPixelSize(); |
| tglDrawCircle(pos, r3); |
| } |
| glPopName(); |
| } |
| |
| |
| |
| void SkeletonTool::drawJoint(const TPointD &pos, bool current) { |
| const double alpha = 0.8, ialpha = 1 - alpha; |
| double r0 = 4 * getPixelSize(); |
| if (current) { |
| glPushName(TD_Center); |
| if (m_device == TD_Center) { |
| glColor4d(0.9 * alpha, 0.8 * alpha, 0.2 * alpha, alpha); |
| r0 *= 1.5; |
| } else { |
| glColor4d(((255.0 / 255.0) - ialpha) / alpha, |
| ((200.0 / 255.0) - ialpha) / alpha, |
| ((48.0 / 255.0) - ialpha) / alpha, alpha); |
| } |
| tglDrawDisk(pos, r0); |
| glColor3d(0.2, 0.1, 0.05); |
| tglDrawCircle(pos, r0); |
| glPopName(); |
| } else { |
| |
| if (m_mode.getValue() == BUILD_SKELETON) |
| glColor4d(0.60 * alpha, 0.60 * alpha, 0.60 * alpha, alpha); |
| else |
| glColor4d(0.78 * alpha, 0.62 * alpha, 0 * alpha, alpha); |
| tglDrawDisk(pos, r0); |
| glColor3d(0.2, 0.1, 0.05); |
| tglDrawCircle(pos, r0); |
| } |
| } |
| |
| |
| |
| void SkeletonTool::drawBone(const TPointD &a, const TPointD &b, bool selected) { |
| const double alpha = 0.8; |
| |
| TPointD delta = b - a; |
| if (norm2(delta) < 0.001) return; |
| TPointD u = getPixelSize() * 2.5 * normalize(rotate90(delta)); |
| if (selected) |
| glColor4d(0.9 * alpha, 0.9 * alpha, 0.9 * alpha, alpha); |
| else |
| glColor4d(0.58 * alpha, 0.58 * alpha, 0.58 * alpha, alpha); |
| glBegin(GL_POLYGON); |
| tglVertex(a + u); |
| tglVertex(b); |
| tglVertex(a - u); |
| glEnd(); |
| glColor3d(0.2, 0.3, 0.35); |
| glBegin(GL_LINE_STRIP); |
| tglVertex(a + u); |
| tglVertex(b); |
| tglVertex(a - u); |
| glEnd(); |
| } |
| |
| |
| |
| void SkeletonTool::drawIKBone(const TPointD &a, const TPointD &b) { |
| |
| TPointD delta = b - a; |
| if (norm2(delta) < 0.001) return; |
| TPointD u = getPixelSize() * 2.5 * normalize(rotate90(delta)); |
| glColor3d(0.58, 0.58, 0.58); |
| glBegin(GL_POLYGON); |
| tglVertex(a + u); |
| tglVertex(b + u); |
| tglVertex(b - u); |
| tglVertex(a - u); |
| glEnd(); |
| glColor3d(0.2, 0.3, 0.35); |
| glBegin(GL_LINES); |
| tglVertex(a + u); |
| tglVertex(b + u); |
| tglVertex(a - u); |
| tglVertex(b - u); |
| glEnd(); |
| } |
| |
| |
| |
| void SkeletonTool::computeMagicLinks() { |
| |
| } |
| |
| |
| |
| void SkeletonTool::drawHooks() { |
| |
| |
| |
| |
| |
| |
| |
| QTime time; |
| time.start(); |
| |
| m_magicLinks.clear(); |
| computeMagicLinks(); |
| |
| TTool::Application *app = TTool::getApplication(); |
| TXsheet *xsh = app->getCurrentXsheet()->getXsheet(); |
| int row = app->getCurrentFrame()->getFrame(); |
| int col = app->getCurrentColumn()->getColumnIndex(); |
| TPointD dpiScale = getViewer()->getDpiScale(); |
| |
| |
| |
| |
| |
| |
| std::set<int> connectedColumns; |
| getConnectedColumns(connectedColumns, xsh, col); |
| |
| std::vector<HookData> currentColumnHooks; |
| std::vector<HookData> otherColumnsHooks; |
| |
| |
| |
| int otherColumn = -1; |
| if (m_parentProbeEnabled) { |
| |
| |
| int hookId = 0; |
| std::string handle = |
| xsh->getStageObject(TStageObjectId::ColumnId(col))->getHandle(); |
| if (handle.find("H") == 0) { |
| int j = 1; |
| while (j < (int)handle.size() && '0' <= handle[j] && handle[j] <= '9') { |
| hookId = hookId * 10 + (int)handle[j] - '0'; |
| j++; |
| } |
| } |
| |
| currentColumnHooks.push_back(HookData(xsh, col, hookId, m_parentProbe)); |
| |
| |
| TPointD parentProbePos = getViewer()->worldToPos(m_parentProbe); |
| std::vector<int> indexes; |
| getViewer()->posToColumnIndexes(parentProbePos, indexes, 10.0, false); |
| for (int i = (int)indexes.size() - 1; i >= 0; i--) { |
| if (connectedColumns.count(indexes[i]) == 0) { |
| otherColumn = indexes[i]; |
| break; |
| } |
| } |
| |
| |
| |
| if (otherColumn < 0 && m_otherColumn >= 0) { |
| if (m_otherColumnBBox.contains(m_otherColumnBBoxAff.inv() * |
| m_parentProbe)) |
| otherColumn = m_otherColumn; |
| else |
| m_otherColumn = -1; |
| } |
| } else { |
| |
| |
| getHooks(currentColumnHooks, xsh, row, col, TPointD(1, 1)); |
| } |
| |
| |
| for (int i = 0; i < xsh->getColumnCount(); i++) |
| if (xsh->getColumn(i)->isCamstandVisible()) |
| if (connectedColumns.count(i) == 0) |
| getHooks(otherColumnsHooks, xsh, row, i, TPointD(1, 1)); |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| std::vector<TRectD> balloons; |
| |
| |
| for (int i = 0; i < (int)currentColumnHooks.size(); i++) { |
| const HookData &hook = currentColumnHooks[i]; |
| if (hook.m_name == "") continue; |
| int code = TD_Hook + hook.m_hookId; |
| TPointD pos = hook.m_pos; |
| ToolUtils::drawHook(pos, ToolUtils::OtherLevelHook); |
| glPushName(code); |
| TPixel32 color(200, 220, 205, 200); |
| if (hook.m_isPivot) |
| color = TPixel32(200, 200, 10, 200); |
| else if (code == m_device) |
| color = TPixel32(185, 255, 255); |
| ToolUtils::drawBalloon(pos, hook.m_name, color, TPoint(20, 20), |
| getPixelSize(), isPicking(), &balloons); |
| glPopName(); |
| } |
| |
| if (m_parentProbeEnabled) { |
| for (int i = 0; i < (int)otherColumnsHooks.size(); i++) |
| ToolUtils::drawHook(otherColumnsHooks[i].m_pos, |
| ToolUtils::OtherLevelHook); |
| } |
| |
| |
| double minDist2 = 0; |
| double snapRadius2 = 100 * getPixelSize() * getPixelSize(); |
| double snapRadius2bis = 100; |
| |
| if (!m_parentProbeEnabled) { |
| |
| for (int i = 0; i < (int)currentColumnHooks.size(); i++) { |
| for (int j = 0; j < (int)otherColumnsHooks.size(); j++) { |
| double dist2 = |
| norm2(currentColumnHooks[i].m_pos - otherColumnsHooks[j].m_pos); |
| if (currentColumnHooks[i].m_hookId == 0 || |
| otherColumnsHooks[j].m_hookId == 0) |
| continue; |
| if (dist2 < snapRadius2bis) { |
| m_magicLinks.push_back( |
| MagicLink(currentColumnHooks[i], otherColumnsHooks[j], dist2)); |
| |
| qDebug(" magic link_a %d (%d,%d) %d (%d,%d); dist=%f", i, |
| currentColumnHooks[i].m_columnIndex, |
| currentColumnHooks[i].m_hookId, j, |
| otherColumnsHooks[j].m_columnIndex, |
| otherColumnsHooks[j].m_hookId, dist2); |
| } |
| } |
| } |
| } |
| |
| if (m_parentProbeEnabled) { |
| |
| int i = -1, j = -1; |
| double minDist2 = snapRadius2; |
| for (i = 0; i < (int)otherColumnsHooks.size(); i++) { |
| double dist2 = tdistance2(otherColumnsHooks[i].m_pos, m_parentProbe); |
| if (dist2 < minDist2) { |
| j = i; |
| minDist2 = dist2; |
| otherColumn = otherColumnsHooks[i].m_columnIndex; |
| } |
| } |
| } |
| |
| if (m_parentProbeEnabled && otherColumn >= 0) |
| { |
| |
| |
| |
| m_otherColumn = otherColumn; |
| getImageBoundingBox(m_otherColumnBBox, m_otherColumnBBoxAff, row, |
| otherColumn); |
| if (!m_otherColumnBBox.isEmpty()) { |
| glPushMatrix(); |
| tglMultMatrix(m_otherColumnBBoxAff); |
| ToolUtils::drawRect(m_otherColumnBBox, TPixel32(188, 202, 191), 0xF0F0); |
| |
| glPopMatrix(); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| int i = -1, j = -1; |
| double minDist2 = snapRadius2; |
| for (i = 0; i < (int)otherColumnsHooks.size(); i++) |
| if (otherColumnsHooks[i].m_columnIndex == otherColumn) { |
| double dist2 = tdistance2(otherColumnsHooks[i].m_pos, m_parentProbe); |
| if (j < 0) |
| j = i; |
| else if (dist2 < minDist2 || otherColumnsHooks[i].m_hookId == 0) { |
| j = i; |
| minDist2 = dist2; |
| } |
| } |
| |
| if (0 <= j && j < (int)otherColumnsHooks.size()) { |
| |
| int alfa = 100, ialfa = 255 - alfa; |
| TPixel32 color(255 * (188 - ialfa) / alfa, 255 * (202 - ialfa) / alfa, |
| 255 * (191 - ialfa) / alfa, alfa); |
| ToolUtils::drawBalloon(otherColumnsHooks[j].m_pos, |
| otherColumnsHooks[j].m_name, |
| color, TPoint(20, 20), getPixelSize(), false, |
| &balloons); |
| |
| HookData baseHook = currentColumnHooks[0]; |
| baseHook.m_pos = otherColumnsHooks[j].m_pos; |
| |
| |
| m_magicLinks.push_back(MagicLink(baseHook, otherColumnsHooks[j], 10000)); |
| } |
| } |
| |
| |
| |
| for (int i = 0; i < (int)m_magicLinks.size(); i++) { |
| const MagicLink &magicLink = m_magicLinks[i]; |
| const HookData &h1 = magicLink.m_h1; |
| std::string name; |
| name = (m_parentProbeEnabled ? "Linking " : "Link ") + |
| removeTrailingH(magicLink.m_h0.getHandle()) + " to Col " + |
| std::to_string(h1.m_columnIndex + 1) + "/" + |
| removeTrailingH(h1.getHandle()); |
| |
| int code = TD_MagicLink + i; |
| glPushName(code); |
| TPixel32 color(100, 255, 100, 100); |
| if (code == m_device) color = TPixel32(185, 255, 255); |
| ToolUtils::drawBalloon(magicLink.m_h0.m_pos, name, color, TPoint(20, -20), |
| getPixelSize(), isPicking(), &balloons); |
| glPopName(); |
| } |
| } |
| |
| |
| |
| void SkeletonTool::drawDrawingBrowser(const TXshCell &cell, |
| const TPointD ¢er) { |
| if (!cell.m_level || cell.m_level->getFrameCount() <= 1) return; |
| double pixelSize = getPixelSize(); |
| |
| std::string name = ::to_string(cell.m_level->getName()) + "." + |
| std::to_string(cell.m_frameId.getNumber()); |
| |
| QString qText = QString::fromStdString(name); |
| QFont font("Arial", 10); |
| QFontMetrics fm(font); |
| QSize textSize = fm.boundingRect(qText).size(); |
| int arrowHeight = 10; |
| int minTextWidth = 2 * arrowHeight + 5; |
| if (textSize.width() < minTextWidth) textSize.setWidth(minTextWidth); |
| QSize totalSize(textSize.width(), textSize.height() + 2 * arrowHeight); |
| TPointD p = center + TPointD(30, -arrowHeight) * pixelSize; |
| QRect textRect(0, arrowHeight, textSize.width(), textSize.height()); |
| |
| assert(glGetError() == 0); |
| |
| if (isPicking()) { |
| double x0 = p.x, x1 = p.x + totalSize.width() * pixelSize; |
| double y0 = p.y, y3 = p.y + totalSize.height() * pixelSize; |
| double y1 = y0 + arrowHeight * pixelSize; |
| double y2 = y3 - arrowHeight * pixelSize; |
| double x = (x0 + x1) * 0.5; |
| double d = arrowHeight * pixelSize; |
| |
| glColor3d(0, 1, 0); |
| glPushName(TD_ChangeDrawing); |
| glRectd(x0, y1, x1, y2); |
| glPopName(); |
| glPushName(TD_IncrementDrawing); |
| glBegin(GL_POLYGON); |
| glVertex2d(x, y0); |
| glVertex2d(x + d, y0 + d); |
| glVertex2d(x - d, y0 + d); |
| glEnd(); |
| glPopName(); |
| glPushName(TD_DecrementDrawing); |
| glBegin(GL_POLYGON); |
| glVertex2d(x, y3); |
| glVertex2d(x + d, y3 - d); |
| glVertex2d(x - d, y3 - d); |
| glEnd(); |
| glPopName(); |
| return; |
| } else { |
| assert(glGetError() == 0); |
| bool active = m_device == TD_ChangeDrawing || |
| m_device == TD_IncrementDrawing || |
| m_device == TD_DecrementDrawing; |
| QImage img(totalSize.width(), totalSize.height(), QImage::Format_ARGB32); |
| img.fill(Qt::transparent); |
| QPainter imgPainter(&img); |
| imgPainter.setRenderHints(QPainter::Antialiasing | |
| QPainter::TextAntialiasing); |
| |
| |
| |
| |
| imgPainter.setPen(Qt::NoPen); |
| imgPainter.setBrush(QColor(200, 200, 200, 200)); |
| imgPainter.drawRect(textRect); |
| imgPainter.setPen(active ? Qt::red : Qt::black); |
| imgPainter.setBrush(Qt::NoBrush); |
| imgPainter.setFont(font); |
| imgPainter.drawText(textRect, Qt::AlignCenter, qText); |
| |
| if (active) { |
| int x = textRect.center().x(); |
| int d = arrowHeight - 4; |
| int y = 0; |
| QPainterPath upArrow; |
| upArrow.moveTo(x, y); |
| upArrow.lineTo(x + d, y + d); |
| upArrow.lineTo(x - d, y + d); |
| upArrow.lineTo(x, y); |
| y = totalSize.height() - 1; |
| QPainterPath dnArrow; |
| dnArrow.moveTo(x, y); |
| dnArrow.lineTo(x + d, y - d); |
| dnArrow.lineTo(x - d, y - d); |
| dnArrow.lineTo(x, y); |
| |
| imgPainter.setPen(Qt::NoPen); |
| imgPainter.setBrush(m_device == TD_DecrementDrawing |
| ? QColor(255, 0, 0) |
| : QColor(200, 100, 100)); |
| imgPainter.drawPath(upArrow); |
| imgPainter.setBrush(m_device == TD_IncrementDrawing |
| ? QColor(255, 0, 0) |
| : QColor(200, 100, 100)); |
| imgPainter.drawPath(dnArrow); |
| } |
| |
| QImage texture = QGLWidget::convertToGLFormat(img); |
| |
| glRasterPos2f(p.x, p.y); |
| |
| glEnable(GL_BLEND); |
| glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); |
| glDrawPixels(texture.width(), texture.height(), GL_RGBA, GL_UNSIGNED_BYTE, |
| texture.bits()); |
| glDisable(GL_BLEND); |
| glColor3d(0, 0, 0); |
| |
| assert(glGetError() == 0); |
| } |
| } |
| |
| |
| |
| void SkeletonTool::drawMainGadget(const TPointD ¢er) { |
| assert(glGetError() == GL_NO_ERROR); |
| |
| double r = 10 * getPixelSize(); |
| double cx = center.x + r * 1.1; |
| double cy = center.y - r * 1.1; |
| |
| glColor3d(1, 0, 0); |
| |
| if (isPicking()) { |
| glPushName(TD_Translation); |
| tglDrawDisk(TPointD(cx, cy), getPixelSize() * 9); |
| glPopName(); |
| return; |
| } |
| |
| QImage img(19, 19, QImage::Format_ARGB32); |
| img.fill(Qt::transparent); |
| QPainter p(&img); |
| |
| |
| QPainterPath pp; |
| int dx = 1, dy = 0; |
| for (int i = 0; i < 4; i++) { |
| int x = 9 + dx * 8; |
| int y = 9 + dy * 8; |
| pp.moveTo(9, 9); |
| pp.lineTo(x, y); |
| pp.lineTo(x - 2 * dx - 2 * dy, y - 2 * dy + 2 * dx); |
| pp.moveTo(x, y); |
| pp.lineTo(x - 2 * dx + 2 * dy, y - 2 * dy - 2 * dx); |
| int d = dx; |
| dx = -dy; |
| dy = d; |
| } |
| |
| p.setPen(QPen(Qt::white, 3)); |
| p.drawPath(pp); |
| p.setPen(Qt::black); |
| p.drawPath(pp); |
| |
| p.setBrush(QColor(54, 213, 54)); |
| p.drawRect(6, 6, 6, 6); |
| QImage texture = QGLWidget::convertToGLFormat(img); |
| |
| |
| glRasterPos2f(center.x + r * 1.1, center.y - r * 1.1); |
| glBitmap(0, 0, 0, 0, -9, -9, NULL); |
| glEnable(GL_BLEND); |
| glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); |
| glDrawPixels(texture.width(), texture.height(), GL_RGBA, GL_UNSIGNED_BYTE, |
| texture.bits()); |
| glDisable(GL_BLEND); |
| glColor3d(0, 0, 0); |
| |
| assert(glGetError() == GL_NO_ERROR); |
| } |
| |
| |
| |
| void SkeletonTool::draw() { |
| |
| |
| |
| |
| if (m_label != "") |
| ToolUtils::drawBalloon(m_labelPos, m_label, TPixel32::Red, TPoint(20, -20), |
| getPixelSize(), false); |
| |
| bool ikEnabled = m_mode.getValue() == INVERSE_KINEMATICS; |
| assert(glGetError() == GL_NO_ERROR); |
| |
| |
| TTool::Application *app = TTool::getApplication(); |
| TXsheet *xsh = getXsheet(); |
| assert(xsh); |
| TStageObjectId objId = app->getCurrentObject()->getObjectId(); |
| |
| if (!objId.isColumn()) return; |
| |
| TStageObject *pegbar = xsh->getStageObject(objId); |
| int col = objId.getIndex(); |
| |
| int frame = app->getCurrentFrame()->getFrame(); |
| if (m_currentFrame != frame) m_temporaryPinnedColumns.clear(); |
| |
| TAffine aff = getMatrix(); |
| |
| if (fabs(aff.det()) < 0.00001) return; |
| |
| |
| |
| if (!ikEnabled) drawLevelBoundingBox(frame, col); |
| |
| glPushMatrix(); |
| tglMultMatrix(aff.inv()); |
| |
| |
| |
| |
| |
| bool changingParent = dynamic_cast<ParentChangeTool *>(m_dragTool) != 0; |
| |
| |
| if (m_mode.getValue() == BUILD_SKELETON && |
| !xsh->getStageObjectParent(objId).isColumn()) { |
| if (!changingParent) drawHooks(); |
| } |
| |
| Skeleton skeleton; |
| buildSkeleton(skeleton, col); |
| |
| glEnable(GL_BLEND); |
| glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); |
| drawSkeleton(skeleton, frame); |
| glDisable(GL_BLEND); |
| |
| TXshCell cell = xsh->getCell(frame, objId.getIndex()); |
| Skeleton::Bone *rootBone = skeleton.getRootBone(); |
| for (int i = 0; i < skeleton.getBoneCount(); i++) { |
| Skeleton::Bone *bone = skeleton.getBone(i); |
| TStageObjectId currentId = bone->getStageObject()->getId(); |
| bool isCurrent = (currentId == objId); |
| TPointD pos = bone->getCenter(); |
| if (isCurrent && m_mode.getValue() != BUILD_SKELETON) { |
| drawDrawingBrowser(cell, pos); |
| } |
| |
| bool isActiveChain = bone->isSelected(); |
| |
| glColor3d(0, 1, 0); |
| if (ikEnabled) { |
| drawIKJoint(bone); |
| } else { |
| TPointD pos = bone->getCenter(); |
| if (isCurrent && m_mode.getValue() == ANIMATE) { |
| drawMainGadget(pos); |
| } |
| } |
| } |
| m_currentFrame = frame; |
| |
| if (m_dragTool) m_dragTool->draw(); |
| glPopMatrix(); |
| } |
| |
| |
| |
| void SkeletonTool::onActivate() { |
| TTool::Application *app = TTool::getApplication(); |
| if (m_firstTime) { |
| m_globalKeyframes.setValue(SkeletonGlobalKeyFrame ? 1 : 0); |
| m_mode.setValue(BUILD_SKELETON); |
| |
| m_firstTime = false; |
| } |
| TStageObjectId objId = app->getCurrentObject()->getObjectId(); |
| if (objId == TStageObjectId::NoneId) { |
| int index = app->getCurrentColumn()->getColumnIndex(); |
| objId = TStageObjectId::ColumnId(index); |
| } |
| |
| } |
| |
| |
| |
| void SkeletonTool::onDeactivate() { |
| |
| |
| } |
| |
| |
| |
| SkeletonSubtools::MagicLink SkeletonTool::getMagicLink(int index) const { |
| assert(0 <= index && index < (int)m_magicLinks.size()); |
| return m_magicLinks[index]; |
| } |
| |
| |
| |
| void SkeletonTool::magicLink(int index) { |
| if (index < 0 || index >= (int)m_magicLinks.size()) return; |
| HookData h0 = m_magicLinks[index].m_h0; |
| HookData h1 = m_magicLinks[index].m_h1; |
| TTool::Application *app = TTool::getApplication(); |
| TXsheet *xsh = app->getCurrentXsheet()->getXsheet(); |
| int columnIndex = app->getCurrentColumn()->getColumnIndex(); |
| TStageObjectId id = TStageObjectId::ColumnId(columnIndex); |
| TStageObject *obj = xsh->getStageObject(id); |
| |
| int parentColumnIndex = h1.m_columnIndex; |
| TStageObjectId parentId = TStageObjectId::ColumnId(parentColumnIndex); |
| std::string parentHandle = h1.getHandle(); |
| |
| std::string handle = ""; |
| if (h0.m_columnIndex < 0) { |
| handle = obj->getHandle(); |
| } else { |
| handle = h0.getHandle(); |
| } |
| |
| |
| |
| TStageObjectCmd::setHandle(id, handle, app->getCurrentXsheet()); |
| TStageObjectCmd::setParent(id, parentId, parentHandle, |
| app->getCurrentXsheet()); |
| |
| } |
| |
| |
| |
| int SkeletonTool::getCursorId() const { |
| switch (m_device) { |
| case TD_None: |
| if (m_mode.getValue() == BUILD_SKELETON) |
| break; |
| else |
| return ToolCursor::RotCursor; |
| case TD_Translation: |
| return ToolCursor::MoveCursor; |
| case TD_Rotation: |
| return ToolCursor::RotCursor; |
| default: |
| return ToolCursor::StrokeSelectCursor; |
| } |
| return ToolCursor::StrokeSelectCursor; |
| } |
| |
| |
| |
| void SkeletonTool::addContextMenuItems(QMenu *menu) { |
| bool ikEnabled = m_mode.getValue() == INVERSE_KINEMATICS; |
| |
| if (ikEnabled) { |
| Skeleton *skeleton = new Skeleton(); |
| buildSkeleton( |
| *skeleton, |
| TTool::getApplication()->getCurrentColumn()->getColumnIndex()); |
| if (skeleton->hasPinnedRanges() || skeleton->isIKEnabled()) { |
| m_commandHandler->setSkeleton(skeleton); |
| QAction *rp = menu->addAction(tr("Reset Pinned Center")); |
| menu->addSeparator(); |
| bool ret = QObject::connect(rp, SIGNAL(triggered()), m_commandHandler, |
| SLOT(clearPinnedRanges())); |
| assert(ret); |
| } else |
| delete skeleton; |
| } |
| } |
| |
| |
| |
| void SkeletonTool::buildSkeleton(Skeleton &skeleton, int columnIndex) { |
| int frame = TTool::getApplication()->getCurrentFrame()->getFrame(); |
| skeleton.build(getXsheet(), frame, columnIndex, m_temporaryPinnedColumns); |
| } |
| |