/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ // Original file Copyright Crytek GMBH or its affiliates, used under license. #include "StdAfx.h" #include "TrackViewSequenceManager.h" #include "TrackViewSplineCtrl.h" #include "TrackViewSequence.h" #include "TrackViewDialog.h" #include "TrackViewTrack.h" #include #include class CUndoTrackViewSplineCtrl : public ISplineCtrlUndo { public: CUndoTrackViewSplineCtrl(CTrackViewSplineCtrl* pCtrl, std::vector& splineContainer) { if (GetIEditor()->GetAnimation()->GetSequence()) { m_sequenceEntityId = GetIEditor()->GetAnimation()->GetSequence()->GetSequenceComponentEntityId(); } m_pCtrl = pCtrl; // Loop over all affected splines for (size_t splineIndex = 0; splineIndex < splineContainer.size(); ++splineIndex) { AddSpline(splineContainer[splineIndex]); } SerializeSplines(&CSplineEntry::m_undo, false); } protected: void AddSpline(ISplineInterpolator* pSpline) { // Find corresponding track and remember it for (size_t trackIndex = 0; trackIndex < m_pCtrl->m_tracks.size(); ++trackIndex) { CTrackViewTrack* pTrack = m_pCtrl->m_tracks[trackIndex]; if (pTrack->GetSpline() == pSpline) { CSplineEntry entry; entry.m_trackId = pTrack->GetId(); m_splineEntries.push_back(entry); } } } virtual int GetSize() { return sizeof(*this); } virtual QString GetDescription() { return "UndoTrackViewSplineCtrl"; }; virtual void Undo(bool bUndo) { CTrackViewSplineCtrl* pCtrl = FindControl(m_pCtrl); if (pCtrl) { pCtrl->SendNotifyEvent(SPLN_BEFORE_CHANGE); } const CTrackViewSequenceManager* sequenceManager = GetIEditor()->GetSequenceManager(); CTrackViewSequence* sequence = sequenceManager->GetSequenceByEntityId(m_sequenceEntityId); AZ_Assert(sequence, "Expected valid sequence."); if (!sequence) { return; } CTrackViewSequenceNotificationContext context(sequence); if (bUndo) { // Save key selection states for redo if necessary m_redoKeyStates = sequence->SaveKeyStates(); SerializeSplines(&CSplineEntry::m_redo, false); } SerializeSplines(&CSplineEntry::m_undo, true); // Undo key selection state sequence->RestoreKeyStates(m_undoKeyStates); if (pCtrl && bUndo) { pCtrl->m_bKeyTimesDirty = true; pCtrl->SendNotifyEvent(SPLN_CHANGE); pCtrl->update(); } if (bUndo) { sequence->OnKeySelectionChanged(); } } virtual void Redo() { const CTrackViewSequenceManager* pSequenceManager = GetIEditor()->GetSequenceManager(); CTrackViewSequence* sequence = pSequenceManager->GetSequenceByEntityId(m_sequenceEntityId); AZ_Assert(sequence, "Expected valid sequence."); if (!sequence) { return; } CTrackViewSequenceNotificationContext context(sequence); CTrackViewSplineCtrl* pCtrl = FindControl(m_pCtrl); if (pCtrl) { pCtrl->SendNotifyEvent(SPLN_BEFORE_CHANGE); } SerializeSplines(&CSplineEntry::m_redo, true); // Redo key selection state sequence->RestoreKeyStates(m_redoKeyStates); if (pCtrl) { pCtrl->m_bKeyTimesDirty = true; pCtrl->SendNotifyEvent(SPLN_CHANGE); pCtrl->update(); } sequence->OnKeySelectionChanged(); } virtual bool IsSelectionChanged() const { const CTrackViewSequenceManager* sequenceManager = GetIEditor()->GetSequenceManager(); CTrackViewSequence* sequence = sequenceManager->GetSequenceByEntityId(m_sequenceEntityId); AZ_Assert(sequence, "Expected valid sequence."); if (!sequence) { return false; } const std::vector currentKeyState = sequence->SaveKeyStates(); return m_undoKeyStates != currentKeyState; } public: typedef std::list CTrackViewSplineCtrls; static CTrackViewSplineCtrl* FindControl(CTrackViewSplineCtrl* pCtrl) { if (!pCtrl) { return 0; } auto iter = std::find(s_activeCtrls.begin(), s_activeCtrls.end(), pCtrl); if (iter == s_activeCtrls.end()) { return 0; } return *iter; } static void RegisterControl(CTrackViewSplineCtrl* pCtrl) { if (!FindControl(pCtrl)) { s_activeCtrls.push_back(pCtrl); } } static void UnregisterControl(CTrackViewSplineCtrl* pCtrl) { if (FindControl(pCtrl)) { s_activeCtrls.remove(pCtrl); } } static CTrackViewSplineCtrls s_activeCtrls; private: class CSplineEntry { public: _smart_ptr m_undo; _smart_ptr m_redo; unsigned int m_trackId; }; void SerializeSplines(_smart_ptr CSplineEntry::* backup, bool bLoading) { const CTrackViewSequenceManager* sequenceManager = GetIEditor()->GetSequenceManager(); CTrackViewSequence* sequence = sequenceManager->GetSequenceByEntityId(m_sequenceEntityId); AZ_Assert(sequence, "Expected valid sequence"); if (!sequence) { return; } CTrackViewSplineCtrl* pCtrl = FindControl(m_pCtrl); for (auto it = m_splineEntries.begin(); it != m_splineEntries.end(); ++it) { CSplineEntry& entry = *it; CTrackViewTrack* track = sequence->FindTrackById(entry.m_trackId); if (track) { ISplineInterpolator* pSpline = track->GetSpline(); if (pSpline && bLoading) { pSpline->Restore(entry.*backup); } else if (pSpline) { (entry.*backup) = pSpline->Backup(); } } } } AZ::EntityId m_sequenceEntityId; CTrackViewSplineCtrl* m_pCtrl; std::vector m_splineEntries; std::vector m_keyTimes; std::vector m_undoKeyStates; std::vector m_redoKeyStates; }; CUndoTrackViewSplineCtrl::CTrackViewSplineCtrls CUndoTrackViewSplineCtrl::s_activeCtrls; ////////////////////////////////////////////////////////////////////////// CTrackViewSplineCtrl::CTrackViewSplineCtrl(QWidget* parent) : SplineWidget(parent) , m_bKeysFreeze(false) , m_bTangentsFreeze(false) , m_stashedRecordModeWhenDraggingTime(false) { CUndoTrackViewSplineCtrl::RegisterControl(this); } CTrackViewSplineCtrl::~CTrackViewSplineCtrl() { CUndoTrackViewSplineCtrl::UnregisterControl(this); } bool CTrackViewSplineCtrl::GetTangentHandlePts(QPoint& inTangentPt, QPoint& pt, QPoint& outTangentPt, int nSpline, int nKey, int nDimension) { ISplineInterpolator* pSpline = m_splines[nSpline].pSpline; CTrackViewTrack* pTrack = m_tracks[nSpline]; float time = pSpline->GetKeyTime(nKey); ISplineInterpolator::ValueType value, tin, tout; ISplineInterpolator::ZeroValue(value); pSpline->GetKeyValue(nKey, value); pSpline->GetKeyTangents(nKey, tin, tout); const CTrackViewKeyHandle& keyHandle = pTrack->GetKey(nKey); if (pTrack->GetCurveType() == eAnimCurveType_TCBFloat) { ITcbKey tcbKey; keyHandle.GetKey(&tcbKey); Vec2 va, vb, vc; va.x = time - 1.0f; va.y = value[nDimension] - tin[nDimension]; vb.x = time; vb.y = value[nDimension]; vc.x = time + 1.0f; vc.y = value[nDimension] + tout[nDimension]; inTangentPt = WorldToClient(va); pt = WorldToClient(vb); outTangentPt = WorldToClient(vc); Vec2 tinv, toutv; float maxLength = float(outTangentPt.x() - pt.x()); tinv.x = float(inTangentPt.x() - pt.x()); tinv.y = float(inTangentPt.y() - pt.y()); toutv.x = float(outTangentPt.x() - pt.x()); toutv.y = float(outTangentPt.y() - pt.y()); tinv.Normalize(); toutv.Normalize(); tinv *= maxLength / (2 - tcbKey.easeto); toutv *= maxLength / (2 - tcbKey.easefrom); inTangentPt = pt + QPoint(int(tinv.x), int(tinv.y)); outTangentPt = pt + QPoint(int(toutv.x), int(toutv.y)); } else { assert(pTrack->GetCurveType() == eAnimCurveType_BezierFloat); assert(nDimension == 0); I2DBezierKey bezierKey; keyHandle.GetKey(&bezierKey); Vec2 va, vb, vc; va.x = time - tin[0]; va.y = value[0] - tin[1]; vb.x = time; vb.y = value[0]; vc.x = time + tout[0]; vc.y = value[0] + tout[1]; inTangentPt = WorldToClient(va); pt = WorldToClient(vb); outTangentPt = WorldToClient(vc); } return true; } void CTrackViewSplineCtrl::ComputeIncomingTangentAndEaseTo(float& ds, float& easeTo, QPoint inTangentPt, int nSpline, int nKey, int nDimension) { ISplineInterpolator* pSpline = m_splines[nSpline].pSpline; CTrackViewTrack* pTrack = m_tracks[nSpline]; float time = pSpline->GetKeyTime(nKey); ISplineInterpolator::ValueType value, tin, tout; ISplineInterpolator::ZeroValue(value); pSpline->GetKeyValue(nKey, value); pSpline->GetKeyTangents(nKey, tin, tout); const CTrackViewKeyHandle& keyHandle = pTrack->GetKey(nKey); ITcbKey tcbKey; keyHandle.GetKey(&tcbKey); // Get the control point. Vec2 tinv, vb; vb.x = time; vb.y = value[nDimension]; QPoint pt = WorldToClient(vb); // Get the max length to compute the 'ease' value. float maxLength = float(WorldToClient(Vec2(vb.x + 1, vb.y)).x() - pt.x()); QPoint tmp = inTangentPt - pt; tinv.x = float(tmp.x()); tinv.y = float(tmp.y()); // Compute the 'easeTo'. easeTo = 2.0f - maxLength / tinv.GetLength(); // Compute the 'ds'. Vec2 va = ClientToWorld(inTangentPt); if (time < va.x + 0.000001f) { if (value[nDimension] > va.y) { ds = 1000000.0f; } else { ds = -1000000.0f; } } else { ds = (value[nDimension] - va.y) / (time - va.x); } } void CTrackViewSplineCtrl::ComputeOutgoingTangentAndEaseFrom(float& dd, float& easeFrom, QPoint outTangentPt, int nSpline, int nKey, int nDimension) { ISplineInterpolator* pSpline = m_splines[nSpline].pSpline; CTrackViewTrack* pTrack = m_tracks[nSpline]; float time = pSpline->GetKeyTime(nKey); ISplineInterpolator::ValueType value, tin, tout; ISplineInterpolator::ZeroValue(value); pSpline->GetKeyValue(nKey, value); pSpline->GetKeyTangents(nKey, tin, tout); const CTrackViewKeyHandle& keyHandle = pTrack->GetKey(nKey); ITcbKey tcbKey; keyHandle.GetKey(&tcbKey); // Get the control point. Vec2 toutv, vb; vb.x = time; vb.y = value[nDimension]; QPoint pt = WorldToClient(vb); // Get the max length to comute the 'ease' value. float maxLength = float(WorldToClient(Vec2(vb.x + 1, vb.y)).x() - pt.x()); QPoint tmp = outTangentPt - pt; toutv.x = float(tmp.x()); toutv.y = float(tmp.y()); // Compute the 'easeFrom'. easeFrom = 2.0f - maxLength / toutv.GetLength(); // Compute the 'dd'. Vec2 vc = ClientToWorld(outTangentPt); if (vc.x < time + 0.000001f) { if (value[nDimension] < vc.y) { dd = 1000000.0f; } else { dd = -1000000.0f; } } else { dd = (vc.y - value[nDimension]) / (vc.x - time); } } void CTrackViewSplineCtrl::AddSpline(ISplineInterpolator* pSpline, CTrackViewTrack* pTrack, const QColor& color) { QColor colorArray[4]; colorArray[0] = colorArray[1] = colorArray[2] = colorArray[3] = color; AddSpline(pSpline, pTrack, colorArray); } void CTrackViewSplineCtrl::AddSpline(ISplineInterpolator* pSpline, CTrackViewTrack* pTrack, QColor anColorArray[4]) { for (int i = 0; i < (int)m_splines.size(); i++) { if (m_splines[i].pSpline == pSpline) { return; } } SSplineInfo si; for (int nCurrentDimension = 0; nCurrentDimension < pSpline->GetNumDimensions(); nCurrentDimension++) { si.anColorArray[nCurrentDimension] = anColorArray[nCurrentDimension]; } si.pSpline = pSpline; si.pDetailSpline = NULL; m_splines.push_back(si); m_tracks.push_back(pTrack); m_bKeyTimesDirty = true; update(); } void CTrackViewSplineCtrl::RemoveAllSplines() { m_tracks.clear(); SplineWidget::RemoveAllSplines(); } void CTrackViewSplineCtrl::MoveSelectedTangentHandleTo(const QPoint& point) { assert(m_pHitSpline && m_nHitKeyIndex >= 0 && m_bHitIncomingHandle >= 0); // Set the custom flag to the key. int nRemoveFlags, nAddFlags; if (m_bHitIncomingHandle) { nRemoveFlags = SPLINE_KEY_TANGENT_IN_MASK; nAddFlags = SPLINE_KEY_TANGENT_CUSTOM << SPLINE_KEY_TANGENT_IN_SHIFT; } else { nRemoveFlags = SPLINE_KEY_TANGENT_OUT_MASK; nAddFlags = SPLINE_KEY_TANGENT_CUSTOM << SPLINE_KEY_TANGENT_OUT_SHIFT; } int flags = m_pHitSpline->GetKeyFlags(m_nHitKeyIndex); flags &= ~nRemoveFlags; flags |= nAddFlags; m_pHitSpline->SetKeyFlags(m_nHitKeyIndex, flags); // Adjust the incoming or outgoing tangent. int splineIndex; for (splineIndex = 0; splineIndex < m_splines.size(); ++splineIndex) { if (m_pHitSpline == m_splines[splineIndex].pSpline) { break; } } assert(splineIndex < m_splines.size()); CTrackViewTrack* pTrack = m_tracks[splineIndex]; CTrackViewKeyHandle keyHandle = pTrack->GetKey(m_nHitKeyIndex); if (pTrack->GetCurveType() == eAnimCurveType_TCBFloat) { if (m_bHitIncomingHandle) { float ds, easeTo; ComputeIncomingTangentAndEaseTo(ds, easeTo, point, splineIndex, m_nHitKeyIndex, m_nHitDimension); // ease-to ITcbKey key; keyHandle.GetKey(&key); key.easeto += easeTo; if (key.easeto > 1) { key.easeto = 1.0f; } else if (key.easeto < 0) { key.easeto = 0; } keyHandle.SetKey(&key); // tin ISplineInterpolator::ValueType tin, tout; m_pHitSpline->GetKeyTangents(m_nHitKeyIndex, tin, tout); tin[m_nHitDimension] = ds; m_pHitSpline->SetKeyInTangent(m_nHitKeyIndex, tin); } else { float dd, easeFrom; ComputeOutgoingTangentAndEaseFrom(dd, easeFrom, point, splineIndex, m_nHitKeyIndex, m_nHitDimension); // ease-from ITcbKey key; keyHandle.GetKey(&key); key.easefrom += easeFrom; if (key.easefrom > 1) { key.easefrom = 1.0f; } else if (key.easefrom < 0) { key.easefrom = 0; } keyHandle.SetKey(&key); // tout ISplineInterpolator::ValueType tin, tout; m_pHitSpline->GetKeyTangents(m_nHitKeyIndex, tin, tout); tout[m_nHitDimension] = dd; m_pHitSpline->SetKeyOutTangent(m_nHitKeyIndex, tout); } } else { assert(pTrack->GetCurveType() == eAnimCurveType_BezierFloat); assert(m_nHitDimension == 0); Vec2 tp = ClientToWorld(point); if (m_bHitIncomingHandle) { // tin ISplineInterpolator::ValueType value, tin, tout; float time = m_pHitSpline->GetKeyTime(m_nHitKeyIndex); m_pHitSpline->GetKeyValue(m_nHitKeyIndex, value); m_pHitSpline->GetKeyTangents(m_nHitKeyIndex, tin, tout); tin[0] = time - tp.x; // Constrain the time range so that the time curve is always monotonically increasing. if (tin[0] < 0) { tin[0] = 0; } else if (m_nHitKeyIndex > 0 && tin[0] > (time - m_pHitSpline->GetKeyTime(m_nHitKeyIndex - 1))) { tin[0] = time - m_pHitSpline->GetKeyTime(m_nHitKeyIndex - 1); } tin[1] = value[0] - tp.y; m_pHitSpline->SetKeyInTangent(m_nHitKeyIndex, tin); } else { // tout ISplineInterpolator::ValueType value, tin, tout; float time = m_pHitSpline->GetKeyTime(m_nHitKeyIndex); m_pHitSpline->GetKeyValue(m_nHitKeyIndex, value); m_pHitSpline->GetKeyTangents(m_nHitKeyIndex, tin, tout); tout[0] = tp.x - time; // Constrain the time range so that the time curve is always monotonically increasing. if (tout[0] < 0) { tout[0] = 0; } else if (m_nHitKeyIndex < m_pHitSpline->GetKeyCount() - 1 && tout[0] > (m_pHitSpline->GetKeyTime(m_nHitKeyIndex + 1) - time)) { tout[0] = m_pHitSpline->GetKeyTime(m_nHitKeyIndex + 1) - time; } tout[1] = tp.y - value[0]; m_pHitSpline->SetKeyOutTangent(m_nHitKeyIndex, tout); } } SendNotifyEvent(SPLN_CHANGE); update(); } void CTrackViewSplineCtrl::mouseMoveEvent(QMouseEvent* event) { const QPoint point = event->pos(); CTrackViewSequence* pSequence = GetIEditor()->GetAnimation()->GetSequence(); if (!pSequence) { return; } CTrackViewSequenceNotificationContext context(pSequence); QPoint cMousePosPrev = m_cMousePos; m_cMousePos = point; if (m_editMode == NothingMode) { switch (HitTest(point)) { case HIT_SPLINE: setCursor(CMFCUtils::LoadCursor(IDC_ARRWHITE)); break; case HIT_KEY: case HIT_TANGENT_HANDLE: setCursor(CMFCUtils::LoadCursor(IDC_ARRBLCK)); break; default: unsetCursor(); break; } } if (m_editMode == SelectMode) { unsetCursor(); QRect rc(m_cMouseDownPos, point); rc = rc.normalized().intersected(m_rcSpline); m_rcSelect = rc; m_rubberBand->setGeometry(m_rcSelect); m_rubberBand->setVisible(true); } if (m_editMode == TimeMarkerMode) { unsetCursor(); SetTimeMarker(XOfsToTime(point.x())); SendNotifyEvent(SPLN_TIME_CHANGE); } if (m_boLeftMouseButtonDown) { if (m_editMode == TrackingMode && point != m_cMouseDownPos) { m_startedDragging = true; GetIEditor()->RestoreUndo(); m_pCurrentUndo = nullptr; StoreUndo(); bool bAltClick = CheckVirtualKey(Qt::Key_Menu); bool bShiftClick = CheckVirtualKey(Qt::Key_Shift); bool bSpaceClick = CheckVirtualKey(Qt::Key_Space); Vec2 v0 = ClientToWorld(m_cMouseDownPos); Vec2 v1 = ClientToWorld(point); if (m_hitCode == HIT_TANGENT_HANDLE) { if (!m_bTangentsFreeze) { MoveSelectedTangentHandleTo(point); } } else if (!m_bKeysFreeze) { if (bAltClick && bShiftClick) { ValueScaleKeys(v0.y, v1.y); } else if (bAltClick) { TimeScaleKeys(m_fTimeMarker, v0.x, v1.x); } else if (bShiftClick) { // Constrains the move to the vertical direction. MoveSelectedKeys(Vec2(0, v1.y - v0.y), false); } else if (bSpaceClick) { // Reset to the original position. MoveSelectedKeys(Vec2(0, 0), false); } else { MoveSelectedKeys(v1 - v0, m_copyKeys); } } } } if (m_editMode == TrackingMode && GetNumSelected() == 1) { float time = 0; ISplineInterpolator::ValueType afValue; QString tipText; bool boFoundTheSelectedKey(false); for (int splineIndex = 0, endSpline = m_splines.size(); splineIndex < endSpline; ++splineIndex) { ISplineInterpolator* pSpline = m_splines[splineIndex].pSpline; CTrackViewTrack* pTrack = m_tracks[splineIndex]; for (int i = 0; i < pSpline->GetKeyCount(); i++) { const CTrackViewKeyHandle& keyHandle = pTrack->GetKey(i); for (int nCurrentDimension = 0; nCurrentDimension < pSpline->GetNumDimensions(); nCurrentDimension++) { if (pSpline->IsKeySelectedAtDimension(i, nCurrentDimension)) { time = pSpline->GetKeyTime(i); pSpline->GetKeyValue(i, afValue); if (pTrack->GetCurveType() == eAnimCurveType_TCBFloat) { ITcbKey key; keyHandle.GetKey(&key); tipText = QStringLiteral("t=%1 v=%2 / T=%3 C=%4 B=%5") .arg(time * m_fTooltipScaleX, 3, 'f').arg(afValue[nCurrentDimension] * m_fTooltipScaleY, 3, 'f', 2) .arg(key.tens, 3, 'f').arg(key.cont, 3, 'f').arg(key.bias, 3, 'f'); } else { assert(pTrack->GetCurveType() == eAnimCurveType_BezierFloat); ISplineInterpolator::ValueType tin, tout; pSpline->GetKeyTangents(i, tin, tout); tipText = QStringLiteral("t=%1 v=%2 / tin=(%3,%4) tout=(%5,%6)") .arg(time * m_fTooltipScaleX, 3, 'f').arg(afValue[0] * m_fTooltipScaleY, 3, 'f', 2) .arg(tin[0], 3, 'f').arg(tin[1], 3, 'f', 2).arg(tout[0], 3, 'f').arg(tout[1], 3, 'f', 2); } boFoundTheSelectedKey = true; break; } } if (boFoundTheSelectedKey) { break; } } } if (point != m_lastToolTipPos) { m_lastToolTipPos = point; m_tooltipText = tipText; update(); } } switch (m_editMode) { case ScrollMode: { // Set the new scrolled coordinates float ofsx = m_grid.origin.x - (point.x() - m_cMouseDownPos.x()) / m_grid.zoom.x; float ofsy = m_grid.origin.y + (point.y() - m_cMouseDownPos.y()) / m_grid.zoom.y; SetScrollOffset(Vec2(ofsx, ofsy)); m_cMouseDownPos = point; } break; case ZoomMode: { float ofsx = (point.x() - m_cMouseDownPos.x()) * 0.01f; float ofsy = (point.y() - m_cMouseDownPos.y()) * 0.01f; Vec2 z = m_grid.zoom; if (ofsx != 0) { z.x = max(z.x * (1.0f + ofsx), 0.001f); } if (ofsy != 0) { z.y = max(z.y * (1.0f + ofsy), 0.001f); } SetZoom(z, m_cMouseDownPos); m_cMouseDownPos = point; } break; } } void CTrackViewSplineCtrl::AdjustTCB(float d_tension, float d_continuity, float d_bias) { CUndo undo("Modify Spline Keys TCB"); ConditionalStoreUndo(); SendNotifyEvent(SPLN_BEFORE_CHANGE); for (int splineIndex = 0, splineCount = m_splines.size(); splineIndex < splineCount; ++splineIndex) { ISplineInterpolator* pSpline = m_splines[splineIndex].pSpline; CTrackViewTrack* pTrack = m_tracks[splineIndex]; if (pTrack->GetCurveType() != eAnimCurveType_TCBFloat) { continue; } for (int i = 0; i < (int)pSpline->GetKeyCount(); i++) { CTrackViewKeyHandle keyHandle = pTrack->GetKey(i); // If the key is selected in any dimension... for ( int nCurrentDimension = 0; nCurrentDimension < pSpline->GetNumDimensions(); nCurrentDimension++ ) { if (IsKeySelected(pSpline, i, nCurrentDimension)) { ITcbKey key; keyHandle.GetKey(&key); // tension key.tens += d_tension; if (key.tens > 1) { key.tens = 1.0f; } else if (key.tens < -1) { key.tens = -1.0f; } // continuity key.cont += d_continuity; if (key.cont > 1) { key.cont = 1.0f; } else if (key.cont < -1) { key.cont = -1.0f; } // bias key.bias += d_bias; if (key.bias > 1) { key.bias = 1.0f; } else if (key.bias < -1) { key.bias = -1.0f; } keyHandle.SetKey(&key); OnUserCommand(ID_TANGENT_AUTO); break; } } } } SendNotifyEvent(SPLN_CHANGE); update(); } void CTrackViewSplineCtrl::OnUserCommand(UINT cmd) { if (cmd == ID_TANGENT_UNIFY) { if (IsUnifiedKeyCurrentlySelected() == false) { ModifySelectedKeysFlags(SPLINE_KEY_TANGENT_ALL_MASK, SPLINE_KEY_TANGENT_UNIFIED); } else { ModifySelectedKeysFlags(SPLINE_KEY_TANGENT_ALL_MASK, SPLINE_KEY_TANGENT_BROKEN); } } else if (cmd == ID_FREEZE_KEYS) { m_bKeysFreeze = !m_bKeysFreeze; } else if (cmd == ID_FREEZE_TANGENTS) { m_bTangentsFreeze = !m_bTangentsFreeze; } else { SplineWidget::OnUserCommand(cmd); } } bool CTrackViewSplineCtrl::IsUnifiedKeyCurrentlySelected() const { for (int splineIndex = 0, splineCount = m_splines.size(); splineIndex < splineCount; ++splineIndex) { ISplineInterpolator* pSpline = m_splines[splineIndex].pSpline; if (pSpline == NULL) { continue; } for (int i = 0; i < (int)pSpline->GetKeyCount(); i++) { // If the key is selected in any dimension... for ( int nCurrentDimension = 0; nCurrentDimension < pSpline->GetNumDimensions(); nCurrentDimension++ ) { if (IsKeySelected(pSpline, i, nCurrentDimension)) { int flags = pSpline->GetKeyFlags(i); if ((flags & SPLINE_KEY_TANGENT_ALL_MASK) != SPLINE_KEY_TANGENT_UNIFIED) { return false; } } } } } return true; } void CTrackViewSplineCtrl::ClearSelection() { // In this case, we should deselect all keys, even ones in other tracks. // So this overriding is necessary. CTrackViewSequence* pSequence = GetIEditor()->GetAnimation()->GetSequence(); if (pSequence) { pSequence->DeselectAllKeys(); } } ISplineCtrlUndo* CTrackViewSplineCtrl::CreateSplineCtrlUndoObject(std::vector& splineContainer) { return new CUndoTrackViewSplineCtrl(this, splineContainer); } void CTrackViewSplineCtrl::mousePressEvent(QMouseEvent* event) { if (GetIEditor()->GetAnimation()->GetSequence()) { SplineWidget::mousePressEvent(event); if (m_editMode == TimeMarkerMode) { // turn off recording when dragging time m_stashedRecordModeWhenDraggingTime = GetIEditor()->GetAnimation()->IsRecordMode(); GetIEditor()->GetAnimation()->SetRecording(false); } } } void CTrackViewSplineCtrl::mouseReleaseEvent(QMouseEvent* event) { if (GetIEditor()->GetAnimation()->GetSequence()) { bool restoreRecordModeToTrue = (m_editMode == TimeMarkerMode && m_stashedRecordModeWhenDraggingTime); SplineWidget::mouseReleaseEvent(event); if (restoreRecordModeToTrue) { // restore recording when dragging time GetIEditor()->GetAnimation()->SetRecording(true); m_stashedRecordModeWhenDraggingTime = false; // reset stashed value } } } void CTrackViewSplineCtrl::mouseDoubleClickEvent(QMouseEvent* event) { CTrackViewSequence* sequence = GetIEditor()->GetAnimation()->GetSequence(); if (sequence) { SplineWidget::mouseDoubleClickEvent(event); if (m_hitCode == HIT_SPLINE) { // HIT_SPLINE results in an added key that gets selected. // Use the selected keys to notify the sequence, key by key, of newly added keys CTrackViewKeyBundle addedKeys = sequence->GetSelectedKeys(); for (int keyIdx = addedKeys.GetKeyCount(); --keyIdx >= 0; ) { CTrackViewKeyHandle addedKey = addedKeys.GetKey(keyIdx); sequence->OnKeyAdded(addedKey); } } } } void CTrackViewSplineCtrl::keyPressEvent(QKeyEvent* event) { auto sequence = GetIEditor()->GetAnimation()->GetSequence(); if (sequence) { // HAVE TO INCLUDE CASES FOR THESE IN THE ShortcutOverride handler in ::event() below if (event->key() == Qt::Key_S && m_playCallback) { m_playCallback(); } else if (event->matches(QKeySequence::Delete)) { CUndo undo("Delete Keys"); SendNotifyEvent(SPLN_BEFORE_CHANGE); sequence->DeleteSelectedKeys(); SendNotifyEvent(SPLN_CHANGE); update(); } else if (event->matches(QKeySequence::Undo)) { GetIEditor()->Undo(); } else if (event->matches(QKeySequence::Redo)) { GetIEditor()->Redo(); } else { SplineWidget::keyPressEvent(event); } } } bool CTrackViewSplineCtrl::event(QEvent* e) { if (e->type() == QEvent::ShortcutOverride) { // since we respond to the following things, let Qt know so that shortcuts don't override us bool respondsToEvent = false; QKeyEvent* keyEvent = static_cast(e); if (keyEvent->key() == Qt::Key_S) { respondsToEvent = true; } else { respondsToEvent = keyEvent->matches(QKeySequence::Delete) || keyEvent->matches(QKeySequence::Undo) || keyEvent->matches(QKeySequence::Redo); } if (respondsToEvent) { e->accept(); return true; } } return SplineWidget::event(e); } void CTrackViewSplineCtrl::wheelEvent(QWheelEvent* event) { if (GetIEditor()->GetAnimation()->GetSequence()) { SplineWidget::wheelEvent(event); } } void CTrackViewSplineCtrl::SelectKey(ISplineInterpolator* pSpline, int nKey, int nDimension, bool bSelect) { SplineWidget::SelectKey(pSpline, nKey, nDimension, bSelect); GetIEditor()->GetAnimation()->GetSequence()->OnKeySelectionChanged(); } void CTrackViewSplineCtrl::SelectRectangle(const QRect& rc, bool bSelect) { SplineWidget::SelectRectangle(rc, bSelect); GetIEditor()->GetAnimation()->GetSequence()->OnKeySelectionChanged(); } void CTrackViewSplineCtrl::SetPlayCallback(const std::function& callback) { m_playCallback = callback; }