/* * 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 "SplineCtrlEx.h" #include "TimelineCtrl.h" #include "Undo/Undo.h" #include "Clipboard.h" #include "GridUtils.h" #include #include #include #include #include #include #include #include #include #define DEFAULT_MIN_TIME_EPSILON 0.001f #define MIN_TIME_EPSILON_FOR_SCALING 0.1f #define ACTIVE_BKG_COLOR QColor(190, 190, 190) #define GRID_COLOR QColor(110, 110, 110) #define EDIT_SPLINE_COLOR QColor(128, 255, 128) #define MIN_PIXEL_PER_GRID_X 50 #define MIN_PIXEL_PER_GRID_Y 10 #define LEFT_BORDER_OFFSET 40 const float AbstractSplineWidget::threshold = 0.015f; static const float minViewRange = 0.1f; ////////////////////////////////////////////////////////////////////////// static void SetKeyTangentType(ISplineInterpolator* pSpline, int key, ESplineKeyTangentType type) { int flags = (pSpline->GetKeyFlags(key) & ~SPLINE_KEY_TANGENT_IN_MASK) & ~SPLINE_KEY_TANGENT_OUT_MASK; pSpline->SetKeyFlags(key, flags | (type << SPLINE_KEY_TANGENT_IN_SHIFT) | (type << SPLINE_KEY_TANGENT_OUT_SHIFT)); } ////////////////////////////////////////////////////////////////////////// class CUndoSplineCtrlEx : public ISplineCtrlUndo { public: CUndoSplineCtrlEx(AbstractSplineWidget* pCtrl, std::vector& splineContainer) { m_pCtrl = FindControl(pCtrl); for (size_t splineIndex = 0; splineIndex < splineContainer.size(); ++splineIndex) { AddSpline(splineContainer[splineIndex]); } SerializeSplines(&SplineEntry::undo, false); } protected: void AddSpline(ISplineInterpolator* pSpline) { AbstractSplineWidget* pCtrl = FindControl(m_pCtrl); m_splineEntries.resize(m_splineEntries.size() + 1); SplineEntry& entry = m_splineEntries.back(); ISplineSet* pSplineSet = (pCtrl ? pCtrl->m_pSplineSet : 0); entry.id = (pSplineSet ? pSplineSet->GetIDFromSpline(pSpline) : 0); entry.pSpline = pSpline; const int numKeys = pSpline->GetKeyCount(); entry.keySelectionFlags.reserve(numKeys); for (int i = 0; i < numKeys; ++i) { entry.keySelectionFlags.push_back(pSpline->GetKeyFlags(i) & ESPLINE_KEY_UI_SELECTED_MASK); } } virtual int GetSize() { return sizeof(*this); } virtual QString GetDescription() { return "UndoSplineCtrlEx"; }; virtual void Undo(bool bUndo) { AbstractSplineWidget* pCtrl = FindControl(m_pCtrl); if (pCtrl) { pCtrl->SendNotifyEvent(SPLN_BEFORE_CHANGE); } if (bUndo) { SerializeSplines(&SplineEntry::redo, false); } SerializeSplines(&SplineEntry::undo, true); if (pCtrl && bUndo) { pCtrl->m_bKeyTimesDirty = true; pCtrl->SendNotifyEvent(SPLN_CHANGE); pCtrl->update(); } } virtual void Redo() { AbstractSplineWidget* pCtrl = FindControl(m_pCtrl); if (pCtrl) { pCtrl->SendNotifyEvent(SPLN_BEFORE_CHANGE); } SerializeSplines(&SplineEntry::redo, true); if (pCtrl) { pCtrl->m_bKeyTimesDirty = true; pCtrl->SendNotifyEvent(SPLN_CHANGE); pCtrl->update(); } } private: class SplineEntry { public: std::vector keySelectionFlags; _smart_ptr undo; _smart_ptr redo; string id; ISplineInterpolator* pSpline; }; void SerializeSplines(_smart_ptr SplineEntry::* backup, bool bLoading) { AbstractSplineWidget* pCtrl = FindControl(m_pCtrl); ISplineSet* pSplineSet = (pCtrl ? pCtrl->m_pSplineSet : 0); for (auto it = m_splineEntries.begin(); it != m_splineEntries.end(); ++it) { SplineEntry& entry = *it; ISplineInterpolator* pSpline = (pSplineSet ? pSplineSet->GetSplineFromID(entry.id) : entry.pSpline); if (!pSpline && pCtrl && pCtrl->GetSplineCount() > 0) { pSpline = pCtrl->GetSpline(0); } if (pSpline && bLoading) { pSpline->Restore(entry.*backup); } else if (pSpline) { (entry.*backup) = pSpline->Backup(); } } } public: typedef std::list CSplineCtrls; static AbstractSplineWidget* FindControl(AbstractSplineWidget* 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(AbstractSplineWidget* pCtrl) { if (!FindControl(pCtrl)) { s_activeCtrls.push_back(pCtrl); } } static void UnregisterControl(AbstractSplineWidget* pCtrl) { if (FindControl(pCtrl)) { s_activeCtrls.remove(pCtrl); } } static CSplineCtrls s_activeCtrls; virtual bool IsSelectionChanged() const { AbstractSplineWidget* pCtrl = FindControl(m_pCtrl); ISplineSet* pSplineSet = (pCtrl ? pCtrl->m_pSplineSet : 0); for (auto it = m_splineEntries.begin(); it != m_splineEntries.end(); ++it) { const SplineEntry& entry = *it; ISplineInterpolator* pSpline = (pSplineSet ? pSplineSet->GetSplineFromID(entry.id) : entry.pSpline); if (!pSpline && pCtrl && pCtrl->GetSplineCount() > 0) { pSpline = pCtrl->GetSpline(0); } if (!pSpline) { return false; } if (pSpline->GetKeyCount() != entry.keySelectionFlags.size()) { return true; } for (int i = 0; i < pSpline->GetKeyCount(); ++i) { if (entry.keySelectionFlags[i] != (pSpline->GetKeyFlags(i) & ESPLINE_KEY_UI_SELECTED_MASK)) { return true; } } } return false; } private: AbstractSplineWidget* m_pCtrl; std::vector m_splineEntries; std::vector m_keyTimes; }; CUndoSplineCtrlEx::CSplineCtrls CUndoSplineCtrlEx::s_activeCtrls; SplineWidget::SplineWidget(QWidget* parent) : QWidget(parent) , m_rubberBand(new QRubberBand(QRubberBand::Rectangle, this)) { m_rubberBand->setVisible(false); setMouseTracking(true); setFocusPolicy(Qt::StrongFocus); } SplineWidget::~SplineWidget() { } ////////////////////////////////////////////////////////////////////////// AbstractSplineWidget::AbstractSplineWidget() : m_defaultKeyTangentType(SPLINE_KEY_TANGENT_NONE) { m_pTimelineCtrl = 0; m_totalSplineCount = 0; m_pHitSpline = 0; m_pHitDetailSpline = 0; m_nHitKeyIndex = -1; m_nHitDimension = -1; m_bHitIncomingHandle = true; m_nKeyDrawRadius = 3; m_gridX = 10; m_gridY = 10; m_timeRange.start = 0; m_timeRange.end = 1; m_fMinValue = -1; m_fMaxValue = 1; m_valueRange.Set(-1, 1); m_fTooltipScaleX = 1; m_fTooltipScaleY = 1; m_cMousePos = QPoint(0, 0); m_fTimeScale = 1; m_fValueScale = 1; m_fGridTimeScale = 30.0f; m_ticksStep = 10; m_fTimeMarker = -10; m_editMode = NothingMode; m_bSnapTime = false; m_bSnapValue = false; m_bBitmapValid = false; m_nLeftOffset = LEFT_BORDER_OFFSET; m_grid.zoom.x = 200; m_grid.zoom.y = 100; m_bKeyTimesDirty = false; m_rcSelect = QRect(); m_rcSpline = QRect(); m_boLeftMouseButtonDown = false; m_pSplineSet = 0; m_controlAmplitude = false; m_fMinTimeEpsilon = DEFAULT_MIN_TIME_EPSILON; m_defaultValueRange.Set(-1.1f, 1.1f); m_bEditLock = false; m_pCurrentUndo = nullptr; CUndoSplineCtrlEx::RegisterControl(this); } ////////////////////////////////////////////////////////////////////////// AbstractSplineWidget::~AbstractSplineWidget() { CUndoSplineCtrlEx::UnregisterControl(this); } ////////////////////////////////////////////////////////////////////////// Vec2 AbstractSplineWidget::GetZoom() { return m_grid.zoom; } Vec2 AbstractSplineWidget::GetScrollOffset() { return m_grid.origin; } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::SetZoom(Vec2 zoom, const QPoint& center) { m_grid.SetZoom(zoom, QPoint(center.x(), m_rcSpline.bottom() + 1 - center.y())); SetScrollOffset(m_grid.origin); if (m_pTimelineCtrl) { m_pTimelineCtrl->setZoom(zoom.x, m_grid.origin.x); } update(); } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::SetZoom(Vec2 zoom) { m_grid.zoom = zoom; SetScrollOffset(m_grid.origin); if (m_pTimelineCtrl) { m_pTimelineCtrl->setZoom(zoom.x, m_grid.origin.x); } SendNotifyEvent(SPLN_SCROLL_ZOOM); update(); } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::SetScrollOffset(Vec2 ofs) { m_grid.origin = ofs; if (m_pTimelineCtrl) { m_pTimelineCtrl->setZoom(m_grid.zoom.x, m_grid.origin.x); } SendNotifyEvent(SPLN_SCROLL_ZOOM); update(); } ////////////////////////////////////////////////////////////////////////// float AbstractSplineWidget::SnapTime(float time) { if (m_bSnapTime) { float step = m_grid.step.x / 10.0f; return floor((time / step) + 0.5f) * step; } return time; } ////////////////////////////////////////////////////////////////////////// float AbstractSplineWidget::SnapValue(float val) { if (m_bSnapValue) { float step = m_grid.step.y; return floor((val / step) + 0.5f) * step; } return val; } ////////////////////////////////////////////////////////////////////////// int AbstractSplineWidget::GetKeyTimeCount() const { UpdateKeyTimes(); return int(m_keyTimes.size()); } ////////////////////////////////////////////////////////////////////////// float AbstractSplineWidget::GetKeyTime(int index) const { UpdateKeyTimes(); return m_keyTimes[index].time; } ////////////////////////////////////////////////////////////////////////// bool AbstractSplineWidget::GetKeyTimeSelected(int index) const { UpdateKeyTimes(); return m_keyTimes[index].selected; } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::SetKeyTimeSelected(int index, bool selected) { m_keyTimes[index].selected = selected; } ////////////////////////////////////////////////////////////////////////// int AbstractSplineWidget::GetKeyCount(int index) const { UpdateKeyTimes(); return m_keyTimes[index].count; } ////////////////////////////////////////////////////////////////////////// int AbstractSplineWidget::GetKeyCountBound() const { UpdateKeyTimes(); return m_totalSplineCount; } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::BeginEdittingKeyTimes() { if (CUndo::IsRecording()) { GetIEditor()->CancelUndo(); m_pCurrentUndo = nullptr; } GetIEditor()->BeginUndo(); for (int keyTimeIndex = 0; keyTimeIndex < int(m_keyTimes.size()); ++keyTimeIndex) { m_keyTimes[keyTimeIndex].oldTime = m_keyTimes[keyTimeIndex].time; } } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::EndEdittingKeyTimes() { if (CUndo::IsRecording()) { GetIEditor()->AcceptUndo("Batch key move"); m_pCurrentUndo = nullptr; } m_bKeyTimesDirty = true; update(); if (m_pTimelineCtrl) { m_pTimelineCtrl->update(); } } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::MoveKeyTimes(int numChanges, int* indices, float scale, float offset, bool copyKeys) { if (CUndo::IsRecording()) { GetIEditor()->RestoreUndo(); std::vector splines; for (int splineIndex = 0; splineIndex < int(m_splines.size()); ++splineIndex) { ISplineInterpolator* pSpline = m_splines[splineIndex].pSpline; if (pSpline) { splines.push_back(pSpline); } } CUndo::Record(m_pCurrentUndo = CreateSplineCtrlUndoObject(splines)); for (int keyTimeIndex = 0; keyTimeIndex < int(m_keyTimes.size()); ++keyTimeIndex) { m_keyTimes[keyTimeIndex].time = m_keyTimes[keyTimeIndex].oldTime; } } class KeyChange { public: KeyChange(ISplineInterpolator* pSpline, int keyIndex, float oldTime, float newTime, int flags) : pSpline(pSpline) , keyIndex(keyIndex) , oldTime(oldTime) , newTime(newTime) , flags(flags) {} ISplineInterpolator* pSpline; int keyIndex; float oldTime; float newTime; ISplineInterpolator::ValueType value; int flags; ISplineInterpolator::ValueType tin, tout; }; std::vector individualKeyChanges; for (int changeIndex = 0; indices && changeIndex < numChanges; ++changeIndex) { int index = (indices ? indices[changeIndex] : 0); float oldTime = m_keyTimes[index].time; float time = __max(m_timeRange.start, __min(m_timeRange.end, scale * oldTime + offset)); for (int splineIndex = 0; splineIndex < int(m_splines.size()); ++splineIndex) { ISplineInterpolator* pSpline = m_splines[splineIndex].pSpline; for (int keyIndex = 0; pSpline && keyIndex < pSpline->GetKeyCount(); ++keyIndex) { float keyTime = pSpline->GetKeyTime(keyIndex); KeyChange change(pSpline, keyIndex, keyTime, SnapTimeToGridVertical(time), pSpline->GetKeyFlags(keyIndex)); pSpline->GetKeyValue(keyIndex, change.value); pSpline->GetKeyTangents(keyIndex, change.tin, change.tout); if (fabsf(keyTime - oldTime) < threshold) { individualKeyChanges.push_back(change); } } } m_keyTimes[index].time = SnapTimeToGridVertical(time); } for (std::vector::iterator itChange = individualKeyChanges.begin(); itChange != individualKeyChanges.end(); ++itChange) { (*itChange).pSpline->SetKeyTime((*itChange).keyIndex, (*itChange).newTime); } if (copyKeys) { for (std::vector::iterator keyToAdd = individualKeyChanges.begin(), endKeysToAdd = individualKeyChanges.end(); keyToAdd != endKeysToAdd; ++keyToAdd) { int keyIndex = (*keyToAdd).pSpline->InsertKey((*keyToAdd).oldTime, (*keyToAdd).value); (*keyToAdd).pSpline->SetKeyTangents(keyIndex, (*keyToAdd).tin, (*keyToAdd).tout); (*keyToAdd).pSpline->SetKeyFlags(keyIndex, (*keyToAdd).flags & (~ESPLINE_KEY_UI_SELECTED_MASK)); } } // Loop through all moved keys, checking whether there are multiple keys on the same frame. for (int splineIndex = 0; splineIndex < int(m_splines.size()); ++splineIndex) { ISplineInterpolator* pSpline = m_splines[splineIndex].pSpline; float lastKeyTime = -FLT_MAX; pSpline->Update(); for (int keyIndex = 0, keys = pSpline->GetKeyCount(); keyIndex <= keys; ) { float keyTime = pSpline->GetKeyTime(keyIndex); if (fabsf(keyTime - lastKeyTime) < m_fMinTimeEpsilon) { --keys; pSpline->RemoveKey(keyIndex); } else { ++keyIndex; lastKeyTime = keyTime; } } } SendNotifyEvent(SPLN_CHANGE); update(); if (m_pTimelineCtrl) { m_pTimelineCtrl->update(); } } ////////////////////////////////////////////////////////////////////////// void SplineWidget::resizeEvent(QResizeEvent* event) { QRect oldRect = m_rcSpline; QWidget::resizeEvent(event); m_rcClient = rect(); m_rcSpline = m_rcClient; if (m_pTimelineCtrl) { QRect rct = m_rcSpline; const int timelineControlHeight = 16; rct.setHeight(timelineControlHeight); m_rcSpline.setTop(rct.bottom() + 1); rct.setLeft(rct.left() + m_nLeftOffset); m_pTimelineCtrl->setGeometry(rct); } m_rcSpline.setLeft(m_rcSpline.left() + m_nLeftOffset); m_grid.rect = m_rcSpline; int oldW = oldRect.width(); int oldH = oldRect.height(); if (width() > 1 && height() > 1 && oldW > 1 && oldH > 1 && m_rcSpline.width() > 0 && m_rcSpline.height()) { SetZoom(Vec2(float(m_rcSpline.width()) / oldW * GetZoom().x, float(m_rcSpline.height()) / oldH * GetZoom().y)); } } ////////////////////////////////////////////////////////////////////////// QPoint AbstractSplineWidget::TimeToPoint(float time, ISplineInterpolator* pSpline) { float val = 0; if (pSpline) { pSpline->InterpolateFloat(time, val); } return WorldToClient(Vec2(time, val)); ; } ////////////////////////////////////////////////////////////////////////// float AbstractSplineWidget::TimeToXOfs(float x) { return WorldToClient(Vec2(float(x), 0.0f)).x(); } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::PointToTimeValue(QPoint point, float& time, float& value) { Vec2 v = ClientToWorld(point); value = v.y; time = XOfsToTime(point.x()); } ////////////////////////////////////////////////////////////////////////// float AbstractSplineWidget::XOfsToTime(int x) { Vec2 v = ClientToWorld(QPoint(x, 0)); float time = v.x; return time; } ////////////////////////////////////////////////////////////////////////// QPoint AbstractSplineWidget::XOfsToPoint(int x, ISplineInterpolator* pSpline) { return TimeToPoint(XOfsToTime(x), pSpline); } ////////////////////////////////////////////////////////////////////////// QPoint AbstractSplineWidget::WorldToClient(Vec2 v) { QPoint p = m_grid.WorldToClient(v); p.setY(m_rcSpline.bottom() - p.y()); return p; } ////////////////////////////////////////////////////////////////////////// Vec2 AbstractSplineWidget::ClientToWorld(const QPoint& point) { Vec2 v = m_grid.ClientToWorld(QPoint(point.x(), m_rcSpline.bottom() + 1 - point.y())); return v; } void SplineWidget::paintEvent(QPaintEvent* event) { QPainter painter(this); if (m_TimeUpdateRect != event->rect()) { painter.fillRect(event->rect(), QColor(160, 160, 160)); m_grid.CalculateGridLines(); //Draw Grid DrawGrid(&painter); const QRect drawSplineRect = event->rect().intersected(m_rcSpline); // Calculate the times corresponding to the left and right of the area to be painted - // we can use this to draw only the necessary parts of the splines. float startTime = XOfsToTime(drawSplineRect.left()); float endTime = XOfsToTime(drawSplineRect.right()); //Draw Keys and Curve for (int i = 0; i < int(m_splines.size()); ++i) { DrawSpline(&painter, m_splines[i], startTime, endTime); DrawKeys(&painter, i, startTime, endTime); } } m_TimeUpdateRect = QRect(); DrawTimeMarker(&painter); } ////////////////////////////////////////////////////////////////////////// class SplineControlVerticalLineDrawer { public: SplineControlVerticalLineDrawer(QPainter* painter, const QRect& rect) : rect(rect) , painter(painter) { } void operator()(int frameIndex, int x) { if (painter) { painter->drawLine(x, rect.top(), x, rect.bottom()); } } QPainter* painter; QRect rect; }; void SplineWidget::DrawGrid(QPainter* painter) { QPoint ptTop = WorldToClient(Vec2(0.0f, m_valueRange.end)); QPoint ptBottom = WorldToClient(Vec2(0.0f, m_valueRange.start)); QPoint pt0 = WorldToClient(Vec2(m_timeRange.start, 0)); QPoint pt1 = WorldToClient(Vec2(m_timeRange.end, 0)); QRect timeRc = QRect(QPoint(pt0.x() - 2, ptTop.y()), QPoint(pt1.x() + 2, ptBottom.y()) - QPoint(1, 1)); timeRc = timeRc.intersected(m_rcSpline); painter->fillRect(timeRc, ACTIVE_BKG_COLOR); ////////////////////////////////////////////////////////////////////////// QPen pOldPen = painter->pen(); painter->setPen(GRID_COLOR); /// Draw Left Separator. painter->fillRect(QRect(QPoint(m_rcClient.left(), m_rcClient.top()), QPoint(m_rcClient.left() + m_nLeftOffset - 1, m_rcClient.bottom()) - QPoint(1, 1)), ACTIVE_BKG_COLOR); painter->drawLine(m_rcClient.left() + m_nLeftOffset, m_rcClient.bottom(), m_rcClient.left() + m_nLeftOffset, m_rcClient.top()); ////////////////////////////////////////////////////////////////////////// int gy; QPen pen(QColor(GRID_COLOR), 1, Qt::DotLine); pen.setCosmetic(true); painter->setPen(pen); //if (m_grid.pixelsPerGrid.y >= MIN_PIXEL_PER_GRID_Y) { // Draw horizontal grid lines. for (gy = m_grid.firstGridLine.y(); gy < m_grid.firstGridLine.y() + m_grid.numGridLines.y() + 1; gy++) { int y = m_grid.GetGridLineY(gy); if (y < 0) { continue; } int py = m_rcSpline.height() - y; if (py < m_rcSpline.top() || py > m_rcSpline.bottom() + 1) { continue; } painter->setPen(pen); painter->drawLine(m_rcSpline.left(), py, m_rcSpline.right(), py); float v = m_grid.GetGridLineYValue(gy); v = floor(v * 1000.0f + 0.5f) / 1000.0f; if ((v >= m_valueRange.start && v <= m_valueRange.end) || fabs(v - m_valueRange.start) < 0.01f || fabs(v - m_valueRange.end) < 0.01f) { painter->setPen(Qt::black); painter->drawText(m_rcClient.left() + 2, py - 8, QString::number(v)); } } } // Draw vertical grid lines. SplineControlVerticalLineDrawer verticalLineDrawer(painter, m_rcSpline); GridUtils::IterateGrid(verticalLineDrawer, 50.0f, m_grid.zoom.x, m_grid.origin.x, m_fGridTimeScale, m_grid.rect.left(), m_grid.rect.right() + 1); ////////////////////////////////////////////////////////////////////////// { const QPen pen0(QColor(110, 100, 100), 2); const QPoint p = WorldToClient(Vec2(0, 0)); painter->setPen(pen0); /// Draw X axis. painter->drawLine(m_rcSpline.left(), p.y(), m_rcSpline.right() + 1, p.y()); // Draw Y Axis. if (p.x() > m_rcSpline.left() && p.y() < m_rcSpline.right() + 1) { painter->drawLine(p.x(), m_rcSpline.top(), p.x(), m_rcSpline.bottom() + 1); } } ////////////////////////////////////////////////////////////////////////// painter->setPen(pOldPen); } ////////////////////////////////////////////////////////////////////////// void SplineWidget::DrawSpline(QPainter* painter, SSplineInfo& splineInfo, float startTime, float endTime) { const QPen pOldPen = painter->pen(); const QRect rcClip = painter->clipBoundingRect().intersected(m_rcSpline).toRect(); ////////////////////////////////////////////////////////////////////////// ISplineInterpolator* pSpline = splineInfo.pSpline; ISplineInterpolator* pDetailSpline = splineInfo.pDetailSpline; if (!pSpline) { return; } int nTotalNumberOfDimensions(0); int nCurrentDimension(0); int left = TimeToXOfs(startTime);//rcClip.left; int right = TimeToXOfs(endTime);//rcClip.right; QPoint p0 = TimeToPoint(pSpline->GetKeyTime(0), pSpline); QPoint p1 = TimeToPoint(pSpline->GetKeyTime(pSpline->GetKeyCount() - 1), pSpline); nTotalNumberOfDimensions = pSpline->GetNumDimensions(); for (nCurrentDimension = 0; nCurrentDimension < nTotalNumberOfDimensions; nCurrentDimension++) { QColor splineColor = EDIT_SPLINE_COLOR; splineColor = splineInfo.anColorArray[nCurrentDimension]; const QPen pen(splineColor, 2); if (p0.x() > left && !pDetailSpline) { QPen alternatePen(splineColor, 1, Qt::DotLine); alternatePen.setCosmetic(true); painter->setPen(alternatePen); painter->drawLine(m_rcSpline.left(), p0.y(), p0.x(), p0.y()); left = p0.x(); } if (p1.x() < right && !pDetailSpline) { QPen alternatePen(splineColor, 1, Qt::DotLine); alternatePen.setCosmetic(true); painter->setPen(alternatePen); painter->drawLine(p1.x(), p1.y(), m_rcSpline.right() + 1, p1.y()); right = p1.x(); } painter->setPen(pen); int linesDrawn = 0; int pixels = 0; float gradient = 0.0f; int pointsInLine = -1; QPoint lineStart; QPainterPath path; for (int x = left; x <= right; x++) { ++pixels; float time = XOfsToTime(x); ISplineInterpolator::ValueType value; ISplineInterpolator::ZeroValue(value); pSpline->Interpolate(time, value); if (pDetailSpline) { ISplineInterpolator::ValueType value2; ISplineInterpolator::ZeroValue(value2); pDetailSpline->Interpolate(time, value2); value[nCurrentDimension] = value[nCurrentDimension] + value2[nCurrentDimension]; } QPoint pt = WorldToClient(Vec2(time, value[nCurrentDimension])); if ((x == right && pointsInLine >= 0) || (pointsInLine > 0 && fabs(lineStart.y() + gradient * (pt.x() - lineStart.x()) - pt.y()) > 1.0f)) { lineStart = QPoint(pt.x() - 1, lineStart.y() + gradient * (pt.x() - 1 - lineStart.x())); path.lineTo(lineStart); gradient = float(pt.y() - lineStart.y()) / (pt.x() - lineStart.x()); pointsInLine = 1; ++linesDrawn; } else if ((x == right && pointsInLine >= 0) || (pointsInLine > 0 && fabs(lineStart.y() + gradient * (pt.x() - lineStart.x()) - pt.y()) == 1.0f)) { lineStart = pt; path.lineTo(lineStart); gradient = 0.0f; pointsInLine = 0; ++linesDrawn; } else if (pointsInLine > 0) { ++pointsInLine; } else if (pointsInLine == 0) { gradient = float(pt.y() - lineStart.y()) / (pt.x() - lineStart.x()); ++pointsInLine; } else { path.moveTo(pt); lineStart = pt; ++pointsInLine; gradient = 0.0f; } } painter->drawPath(path); // Put back the old objects painter->setPen(pOldPen); } } ////////////////////////////////////////////////////////////////////////// void SplineWidget::DrawKeys(QPainter* painter, int splineIndex, float startTime, float endTime) { SSplineInfo& splineInfo = m_splines[splineIndex]; ISplineInterpolator* pSpline = splineInfo.pSpline; ISplineInterpolator* pDetailSpline = splineInfo.pDetailSpline; if (!pSpline) { return; } // create and select a white pen - nope, black const QPen pOldPen = painter->pen(); painter->setPen(Qt::black); int i; int nTotalNumberOfDimensions(0); int nCurrentDimension(0); nTotalNumberOfDimensions = pSpline->GetNumDimensions(); for (nCurrentDimension = 0; nCurrentDimension < nTotalNumberOfDimensions; nCurrentDimension++) { // Why is this here? Not even god knows... //for (i = 0; i < pSpline->GetKeyCount() && pSpline->GetKeyTime(i) < startTime; ++i); QPoint lastKeyPt; int numKeys = pSpline->GetKeyCount(); for (i = 0; i < numKeys; i++) { float time = pSpline->GetKeyTime(i); if (time >= endTime) { break; } ISplineInterpolator::ValueType value; ISplineInterpolator::ZeroValue(value); pSpline->Interpolate(time, value); if (pDetailSpline) { ISplineInterpolator::ValueType value2; ISplineInterpolator::ZeroValue(value2); pDetailSpline->Interpolate(time, value2); value[nCurrentDimension] = value[nCurrentDimension] + value2[nCurrentDimension]; } QPoint pt = WorldToClient(Vec2(time, value[nCurrentDimension])); ; if (pt.x() < m_rcSpline.left()) { continue; } if (i > 0 && (pt - lastKeyPt).manhattanLength() < 4) { continue; } QColor clr(220, 220, 0); if (pSpline->IsKeySelectedAtDimension(i, nCurrentDimension)) { clr = Qt::red; DrawTangentHandle(painter, splineIndex, i, nCurrentDimension); } const QBrush brush(clr); const QBrush pOldBrush = painter->brush(); painter->setBrush(brush); // Draw this key. painter->drawRect(QRect(QPoint(pt.x() - m_nKeyDrawRadius, pt.y() - m_nKeyDrawRadius), QPoint(pt.x() + m_nKeyDrawRadius - 1, pt.y() + m_nKeyDrawRadius - 1))); lastKeyPt = pt; painter->setBrush(pOldBrush); } } painter->setPen(pOldPen); } bool AbstractSplineWidget::GetTangentHandlePts(QPoint&, QPoint&, QPoint&, int, int, int) { return false; } void SplineWidget::DrawTangentHandle(QPainter* painter, int nSpline, int nKey, int nDimension) { // create and select a white pen const QPen pOldPen = painter->pen(); painter->setPen(QColor(96, 96, 96)); // Draw in-tangent & out-tangent lines. QPoint a, b, pt; if (GetTangentHandlePts(a, pt, b, nSpline, nKey, nDimension)) { painter->drawLine(a, pt); painter->drawLine(pt, b); // Draw end-effectors. const QBrush pOldBrush = painter->brush(); painter->setBrush(QColor(0, 220, 0)); painter->drawRect(QRect(QPoint(a.x() - m_nKeyDrawRadius, a.y() - m_nKeyDrawRadius), QPoint(a.x() + m_nKeyDrawRadius - 1, a.y() + m_nKeyDrawRadius - 1))); painter->drawRect(QRect(QPoint(b.x() - m_nKeyDrawRadius, b.y() - m_nKeyDrawRadius), QPoint(b.x() + m_nKeyDrawRadius - 1, b.y() + m_nKeyDrawRadius - 1))); painter->setBrush(pOldBrush); } painter->setPen(pOldPen); } ////////////////////////////////////////////////////////////////////////// void SplineWidget::DrawTimeMarker(QPainter* painter) { const QPen pOldPen = painter->pen(); painter->setPen(QColor(255, 0, 255)); float x = TimeToXOfs(m_fTimeMarker); if (x >= m_rcSpline.left() && x <= m_rcSpline.right() + 1) { painter->drawLine(x, m_rcSpline.top(), x, m_rcSpline.bottom() + 1); } painter->setPen(pOldPen); } ///////////////////////////////////////////////////////////////////////////// //Mouse Message Handlers ////////////////////////////////////////////////////////////////////////// void SplineWidget::mousePressEvent(QMouseEvent* event) { switch (event->button()) { case Qt::LeftButton: OnLButtonDown(event->pos(), event->modifiers()); break; case Qt::MiddleButton: OnMButtonDown(event->pos(), event->modifiers()); break; case Qt::RightButton: OnRButtonDown(event->pos(), event->modifiers()); break; } } void SplineWidget::mouseReleaseEvent(QMouseEvent* event) { switch (event->button()) { case Qt::LeftButton: OnLButtonUp(event->pos(), event->modifiers()); break; case Qt::MiddleButton: OnMButtonUp(event->pos(), event->modifiers()); break; } } void SplineWidget::OnLButtonDown(const QPoint& point, Qt::KeyboardModifiers modifiers) { m_pCurrentUndo = nullptr; if (m_bEditLock) { return; } m_boLeftMouseButtonDown = true; if (m_editMode == TrackingMode) { return; } SendNotifyEvent(NM_CLICK); m_cMouseDownPos = point; ISplineInterpolator* pSpline = HitSpline(m_cMouseDownPos); // Get control key status. bool bCtrlClick = modifiers & Qt::ControlModifier; switch (m_hitCode) { case HIT_KEY: { { CUndo undo("Select Spline Key"); StoreUndo(); SendNotifyEvent(SPLN_BEFORE_CHANGE); bool bHitSelection = IsKeySelected(m_pHitSpline, m_nHitKeyIndex, m_nHitDimension); bool bAddSelect = bCtrlClick; if (!bAddSelect && !bHitSelection) { ClearSelection(); } SelectKey(pSpline, m_nHitKeyIndex, m_nHitDimension, true); SendNotifyEvent(SPLN_CHANGE); if (m_pCurrentUndo && !m_pCurrentUndo->IsSelectionChanged()) { undo.Cancel(); } m_pCurrentUndo = nullptr; } GetIEditor()->BeginUndo(); StartTracking(bCtrlClick); } break; case HIT_TANGENT_HANDLE: { { CUndo undo("Select Tangent Handle"); SendNotifyEvent(SPLN_BEFORE_CHANGE); ClearSelection(); SelectKey(pSpline, m_nHitKeyIndex, m_nHitDimension, true); SendNotifyEvent(SPLN_CHANGE); } StartTracking(false); } break; case HIT_SPLINE: { if (GetNumSelected() > 0) { StartTracking(bCtrlClick); } } break; case HIT_TIMEMARKER: { SendNotifyEvent(SPLN_TIME_START_CHANGE); m_editMode = TimeMarkerMode; grabMouse(); } break; case HIT_NOTHING: { if (m_rcSpline.contains(point)) { GetIEditor()->BeginUndo(); StoreUndo(); m_rcSelect = QRect(); m_rubberBand->setVisible(false); m_editMode = SelectMode; grabMouse(); } } break; } update(); } ////////////////////////////////////////////////////////////////////////// void SplineWidget::OnRButtonDown(const QPoint&, Qt::KeyboardModifiers) { m_pCurrentUndo = nullptr; SendNotifyEvent(NM_RCLICK); } ////////////////////////////////////////////////////////////////////////// void SplineWidget::OnMButtonDown(const QPoint& point, Qt::KeyboardModifiers modifiers) { m_pCurrentUndo = nullptr; bool bShiftClick = modifiers & Qt::ShiftModifier; if (m_editMode == NothingMode) { if (bShiftClick) { m_editMode = ZoomMode; setCursor(Qt::SizeAllCursor); } else { setCursor(Qt::SizeAllCursor); m_editMode = ScrollMode; } m_cMouseDownPos = point; } } ////////////////////////////////////////////////////////////////////////// void SplineWidget::OnMButtonUp(const QPoint&, Qt::KeyboardModifiers) { if (m_editMode == ScrollMode || m_editMode == ZoomMode) { m_editMode = NothingMode; return; } } ////////////////////////////////////////////////////////////////////////// void SplineWidget::mouseDoubleClickEvent(QMouseEvent* event) { if (event->button() != Qt::LeftButton) { return; } const QPoint point = event->pos(); m_pCurrentUndo = nullptr; if (m_bEditLock) { return; } switch (m_hitCode) { case HIT_SPLINE: { if (m_pHitSpline) { InsertKey(m_pHitSpline, m_pHitDetailSpline, point); } update(); if (m_pTimelineCtrl) { m_pTimelineCtrl->update(); } } break; case HIT_KEY: { RemoveKey(m_pHitSpline, m_nHitKeyIndex); } break; case HIT_TANGENT_HANDLE: { if (m_bHitIncomingHandle) { ModifySelectedKeysFlags(SPLINE_KEY_TANGENT_IN_MASK, 0); } else { ModifySelectedKeysFlags(SPLINE_KEY_TANGENT_OUT_MASK, 0); } } break; } } ////////////////////////////////////////////////////////////////////////// void SplineWidget::mouseMoveEvent(QMouseEvent* event) { const QPoint point = event->pos(); 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: setCursor(Qt::ArrowCursor); break; } if (m_pHitSpline && m_nHitKeyIndex >= 0) { float time = m_pHitSpline->GetKeyTime(m_nHitKeyIndex); ISplineInterpolator::ValueType afValue; m_pHitSpline->GetKeyValue(m_nHitKeyIndex, afValue); const QString tipText = QString::fromLatin1("t=%1 v=%2").arg(time * m_fTooltipScaleX, 0, 'f', 3).arg(afValue[m_nHitDimension] * m_fTooltipScaleY, 2, 'f', 3); m_tooltipText = tipText; if (m_lastToolTipPos != point) { m_lastToolTipPos = point; QToolTip::showText(event->globalPos(), tipText); } } else if (m_editMode != TrackingMode) { if (!m_tooltipText.isEmpty()) { QToolTip::hideText(); } } if (m_bEditLock) { return; } m_cMousePos = event->pos(); if (m_editMode == SelectMode) { setCursor(Qt::BlankCursor); QRect rc(QPoint(m_cMouseDownPos.x(), m_cMouseDownPos.y()), point - QPoint(1, 1)); rc = rc.normalized().intersected(m_rcSpline); m_rcSelect = rc; m_rubberBand->setGeometry(m_rcSelect); m_rubberBand->setVisible(true); // Trigger a repaint so that the spline lines will be drawn correctly, // or else they end up being chopped up if you drag the selection area // over them update(); } if (m_editMode == TimeMarkerMode) { setCursor(Qt::BlankCursor); SetTimeMarker(XOfsToTime(event->x())); SendNotifyEvent(SPLN_TIME_CHANGE); } if (m_boLeftMouseButtonDown) { if (m_editMode == TrackingMode && event->pos() != m_cMouseDownPos) { m_startedDragging = true; GetIEditor()->RestoreUndo(); m_pCurrentUndo = nullptr; StoreUndo(); bool bAltClick = event->modifiers() & Qt::AltModifier; Vec2 v0 = ClientToWorld(m_cMouseDownPos); Vec2 v1 = ClientToWorld(event->pos()); if (bAltClick) { TimeScaleKeys(m_fTimeMarker, v0.x, v1.x); } else if (m_controlAmplitude) { ScaleAmplitudeKeys(v0.x, v0.y, v1.y - v0.y); } else { MoveSelectedKeys(v1 - v0, m_copyKeys); } } } switch (m_editMode) { case ScrollMode: { // Set the new scrolled coordinates float ofsx = m_grid.origin.x - (event->x() - m_cMouseDownPos.x()) / m_grid.zoom.x; float ofsy = m_grid.origin.y + (event->y() - m_cMouseDownPos.y()) / m_grid.zoom.y; SetScrollOffset(Vec2(ofsx, ofsy)); m_cMouseDownPos = event->pos(); } break; case ZoomMode: { float ofsx = (event->x() - m_cMouseDownPos.x()) * 0.01f; float ofsy = (event->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 = event->pos(); } break; } } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::UpdateKeyTimes() const { if (!m_bKeyTimesDirty) { return; } std::vector selectedKeyTimes; selectedKeyTimes.reserve(m_keyTimes.size()); for (std::vector::iterator it = m_keyTimes.begin(), end = m_keyTimes.end(); it != end; ++it) { if ((*it).selected) { selectedKeyTimes.push_back((*it).time); } } std::sort(selectedKeyTimes.begin(), selectedKeyTimes.end()); m_keyTimes.clear(); for (int splineIndex = 0; splineIndex < int(m_splines.size()); ++splineIndex) { ISplineInterpolator* pSpline = m_splines[splineIndex].pSpline; for (int keyIndex = 0; pSpline && keyIndex < pSpline->GetKeyCount(); ++keyIndex) { float value = pSpline->GetKeyTime(keyIndex); int lower = 0; int upper = int(m_keyTimes.size()); while (lower < upper - 1) { int mid = ((lower + upper) >> 1); ((m_keyTimes[mid].time >= value) ? upper : lower) = mid; } if ((lower >= int(m_keyTimes.size()) || fabsf(m_keyTimes[lower].time - value) > threshold) && (upper >= int(m_keyTimes.size()) || fabsf(m_keyTimes[upper].time - value) > threshold)) { m_keyTimes.insert(m_keyTimes.begin() + upper, KeyTime(value, 0)); } } } for (std::vector::iterator it = m_keyTimes.begin(), end = m_keyTimes.end(); it != end; ++it) { (*it).count = (m_pSplineSet ? m_pSplineSet->GetKeyCountAtTime((*it).time, threshold) : 0); } std::vector::iterator itSelected = selectedKeyTimes.begin(), endSelected = selectedKeyTimes.end(); for (std::vector::iterator it = m_keyTimes.begin(), end = m_keyTimes.end(); it != end; ++it) { const float threshold = 0.01f; for (; itSelected != endSelected && (*itSelected) < (*it).time - threshold; ++itSelected) { ; } if (itSelected != endSelected && fabsf((*itSelected) - (*it).time) < threshold) { (*it).selected = true; } } m_totalSplineCount = (m_pSplineSet ? m_pSplineSet->GetSplineCount() : 0); m_bKeyTimesDirty = false; } ////////////////////////////////////////////////////////////////////////// void SplineWidget::OnLButtonUp(const QPoint& point, Qt::KeyboardModifiers modifiers) { if (m_bEditLock) { return; } m_boLeftMouseButtonDown = false; if (m_editMode == TrackingMode) { StopTracking(); if (!m_startedDragging) { HitSpline(m_cMouseDownPos); } } if (m_editMode == SelectMode) { // Get control key status. bool bAltClick = modifiers & Qt::AltModifier; bool bCtrlClick = modifiers & Qt::ControlModifier; bool bAddSelect = bCtrlClick; bool bUnselect = bAltClick; // Stop tracking first or else the clear selection will end up being // undone StopTracking(); if (!bAddSelect && !bUnselect) { ClearSelection(); } SelectRectangle(m_rcSelect, !bUnselect); m_rcSelect = QRect(); m_rubberBand->setVisible(false); setCursor(Qt::ArrowCursor); } if (m_editMode == TimeMarkerMode) { m_editMode = NothingMode; releaseMouse(); SendNotifyEvent(SPLN_TIME_END_CHANGE); } if (m_pTimelineCtrl) { m_pTimelineCtrl->update(); } m_tooltipText = ""; update(); m_editMode = NothingMode; } ///////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::SelectKey(ISplineInterpolator* pSpline, int nKey, int nDimension, bool bSelect) { if (nKey >= 0) { pSpline->SelectKeyAtDimension(nKey, nDimension, bSelect); SendNotifyEvent(SPLN_KEY_SELECTION_CHANGE); } } ////////////////////////////////////////////////////////////////////////// bool AbstractSplineWidget::IsKeySelected(ISplineInterpolator* pSpline, int nKey, int nHitDimension) const { if (pSpline && nKey >= 0) { return (pSpline->IsKeySelectedAtDimension(nKey, nHitDimension)); } return false; } ////////////////////////////////////////////////////////////////////////// int AbstractSplineWidget::GetNumSelected() { int nSelected = 0; for (int splineIndex = 0, splineCount = m_splines.size(); splineIndex < splineCount; ++splineIndex) { if (ISplineInterpolator* pSpline = m_splines[splineIndex].pSpline) { for (int i = 0; i < (int)pSpline->GetKeyCount(); i++) { for (int nCurrentDimension = 0; nCurrentDimension < pSpline->GetNumDimensions(); nCurrentDimension++) { if (pSpline->IsKeySelectedAtDimension(i, nCurrentDimension)) { nSelected++; } } } } } return nSelected; } ///////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::SetSplineSet(ISplineSet* pSplineSet) { m_pSplineSet = pSplineSet; } ////////////////////////////////////////////////////////////////////////// void SplineWidget::wheelEvent(QWheelEvent* event) { int zDelta = event->angleDelta().y(); if (zDelta == 0) { return; } Vec2 z = m_grid.zoom; float scale = 1.2f * fabs(zDelta / 120.0f); if (zDelta > 0) { z *= scale; } else { z /= scale; } SetZoom(z, m_cMousePos); event->accept(); } void SplineWidget::keyPressEvent(QKeyEvent* e) { BOOL bProcessed = false; switch (e->key()) { case Qt::Key_Delete: { RemoveSelectedKeys(); RemoveSelectedKeyTimes(); bProcessed = true; } break; case Qt::Key_Up: { CUndo undo("Move Spline Key(s)"); SendNotifyEvent(SPLN_BEFORE_CHANGE); MoveSelectedKeys(ClientToWorld(QPoint(0, -1)), false); bProcessed = true; } break; case Qt::Key_Down: { CUndo undo("Move Spline Key(s)"); SendNotifyEvent(SPLN_BEFORE_CHANGE); MoveSelectedKeys(ClientToWorld(QPoint(0, 1)), false); bProcessed = true; } break; case Qt::Key_Left: { CUndo undo("Move Spline Key(s)"); SendNotifyEvent(SPLN_BEFORE_CHANGE); MoveSelectedKeys(ClientToWorld(QPoint(-1, 0)), false); bProcessed = true; } break; case Qt::Key_Right: { CUndo undo("Move Spline Key(s)"); SendNotifyEvent(SPLN_BEFORE_CHANGE); MoveSelectedKeys(ClientToWorld(QPoint(1, 0)), false); bProcessed = true; } break; default: break; //do nothing } bool bCtrl = e->modifiers() & Qt::ControlModifier; if (e->key() == Qt::Key_C && bCtrl) { CopyKeys(); return; } if (e->key() == Qt::Key_V && bCtrl) { PasteKeys(); return; } if (e->key() == Qt::Key_Z && bCtrl) { GetIEditor()->Undo(); return; } if (e->key() == Qt::Key_Y && bCtrl) { GetIEditor()->Redo(); return; } if (!bProcessed) { QWidget::keyPressEvent(e); } } bool SplineWidget::event(QEvent* e) { // Shortcut override events are sent to allow a widget to say that it wants to get the matching // keypress event, and for it not to be treated as a shortcut by the shortcut/QAction system. // So we need to handle it here, return true and mark the event as accepted, if it's a key combination we care // about if (e->type() == QEvent::ShortcutOverride) { QKeyEvent* keyEvent = static_cast(e); bool filterOutEvent = false; switch (keyEvent->key()) { case Qt::Key_Delete: case Qt::Key_Up: case Qt::Key_Down: case Qt::Key_Left: case Qt::Key_Right: filterOutEvent = true; break; case Qt::Key_C: case Qt::Key_V: case Qt::Key_Z: case Qt::Key_Y: filterOutEvent = (keyEvent->modifiers() & Qt::ControlModifier) != 0; break; default: break; //do nothing } if (filterOutEvent) { e->accept(); return true; } } return QWidget::event(e); } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::SetHorizontalExtent(int min, int max) { /* m_scrollMin.x = min; m_scrollMax.x = max; int width = max - min; int nPage = m_rcClient.Width()/2; int sx = width - nPage + m_rcSpline.left; SCROLLINFO si; ZeroStruct(si); si.cbSize = sizeof(si); si.fMask = SIF_ALL; si.nMin = m_scrollMin.x; si.nMax = m_scrollMax.x - nPage + m_rcSpline.left; si.nPage = m_rcClient.Width()/2; si.nPos = m_scrollOffset.x; //si.nPage = max(0,m_rcClient.Width() - m_leftOffset*2); //si.nPage = 1; //si.nPage = 1; SetScrollInfo( SB_HORZ,&si,TRUE ); */ } ////////////////////////////////////////////////////////////////////////// ISplineInterpolator* AbstractSplineWidget::HitSpline(const QPoint& point) { if (HitTest(point) != HIT_NOTHING) { return m_pHitSpline; } return NULL; } ////////////////////////////////////////////////////////////////////////////// AbstractSplineWidget::EHitCode AbstractSplineWidget::HitTest(const QPoint& point) { float time, val; int nTotalNumberOfDimensions(0); int nCurrentDimension(0); PointToTimeValue(point, time, val); m_hitCode = HIT_NOTHING; m_pHitSpline = NULL; m_pHitDetailSpline = NULL; m_nHitKeyIndex = -1; m_nHitDimension = -1; m_bHitIncomingHandle = true; if (abs(point.x() - TimeToXOfs(m_fTimeMarker)) < 4) { m_hitCode = HIT_TIMEMARKER; } // For each Spline... for (int splineIndex = 0, splineCount = m_splines.size(); splineIndex < splineCount; ++splineIndex) { ISplineInterpolator* pSpline = m_splines[splineIndex].pSpline; ISplineInterpolator* pDetailSpline = m_splines[splineIndex].pDetailSpline; // If there is no spline, you can't hit nor a spline nor a key... isn't that logical? if (!pSpline) { return m_hitCode; } ISplineInterpolator::ValueType stSplineValue; ISplineInterpolator::ValueType stDetailSplineValue; ISplineInterpolator::ZeroValue(stSplineValue); ISplineInterpolator::ZeroValue(stDetailSplineValue); pSpline->Interpolate(time, stSplineValue); if (pDetailSpline) { pDetailSpline->Interpolate(time, stDetailSplineValue); } // For each dimension... nTotalNumberOfDimensions = pSpline->GetNumDimensions(); for (nCurrentDimension = 0; nCurrentDimension < nTotalNumberOfDimensions; nCurrentDimension++) { if (pDetailSpline) { stSplineValue[nCurrentDimension] = stSplineValue[nCurrentDimension] + stDetailSplineValue[nCurrentDimension]; } for (int i = 0; i < pSpline->GetKeyCount(); i++) { if (pSpline->IsKeySelectedAtDimension(i, nCurrentDimension)) // Check tangent handles first. { QPoint incomingHandlePt, outgoingHandlePt, pt; if (GetTangentHandlePts(incomingHandlePt, pt, outgoingHandlePt, splineIndex, i, nCurrentDimension)) { // For the incoming handle if (abs(incomingHandlePt.x() - point.x()) < 4 && abs(incomingHandlePt.y() - point.y()) < 4) { m_hitCode = HIT_TANGENT_HANDLE; m_pHitSpline = pSpline; m_pHitDetailSpline = m_splines[splineIndex].pDetailSpline; m_nHitKeyIndex = i; m_nHitDimension = nCurrentDimension; m_bHitIncomingHandle = true; return m_hitCode; } // For the outgoing handle else if (abs(outgoingHandlePt.x() - point.x()) < 4 && abs(outgoingHandlePt.y() - point.y()) < 4) { m_hitCode = HIT_TANGENT_HANDLE; m_pHitSpline = pSpline; m_pHitDetailSpline = m_splines[splineIndex].pDetailSpline; m_nHitKeyIndex = i; m_nHitDimension = nCurrentDimension; m_bHitIncomingHandle = false; return m_hitCode; } } } } QPoint splinePt = WorldToClient(Vec2(time, stSplineValue[nCurrentDimension])); bool bSplineHit = abs(splinePt.x() - point.x()) < 4 && abs(splinePt.y() - point.y()) < 4; if (bSplineHit) { m_hitCode = HIT_SPLINE; m_pHitSpline = pSpline; m_pHitDetailSpline = m_splines[splineIndex].pDetailSpline; for (int i = 0; i < pSpline->GetKeyCount(); i++) { QPoint splinePt = TimeToPoint(pSpline->GetKeyTime(i), pSpline); if (abs(splinePt.x() - point.x()) < 4 /* && abs(splinePt.y()-point.y()) < 4*/) { m_nHitKeyIndex = i; m_nHitDimension = nCurrentDimension; m_hitCode = HIT_KEY; return m_hitCode; } } } } } return m_hitCode; } /////////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::StartTracking(bool copyKeys) { m_copyKeys = copyKeys; m_startedDragging = false; m_editMode = TrackingMode; captureMouseImpl(); GetIEditor()->BeginUndo(); SendNotifyEvent(SPLN_BEFORE_CHANGE); setCursorImpl(IDC_ARRBLCKCROSS); } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::StopTracking() { if ((m_editMode == TrackingMode) && (m_cMousePos != m_cMouseDownPos)) { GetIEditor()->AcceptUndo("Spline Move"); } else if (m_pCurrentUndo && (m_cMousePos == m_cMouseDownPos)) { if (m_editMode == SelectMode) { if (m_pCurrentUndo->IsSelectionChanged()) { GetIEditor()->AcceptUndo("Key Selection"); } else { GetIEditor()->CancelUndo(); } } else if ((m_editMode == TrackingMode)) { GetIEditor()->CancelUndo(); } } else { GetIEditor()->CancelUndo(); } // Undo has been accepted or cancelled, so clear the m_pCurrentUndo pointer. m_pCurrentUndo = nullptr; m_editMode = NothingMode; releaseMouseImpl(); update(); } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::ScaleAmplitudeKeys(float time, float startValue, float offset) { //TODO: Test it in the facial animation pane and fix it... m_pHitSpline = 0; m_pHitDetailSpline = 0; m_nHitKeyIndex = -1; m_nHitDimension = -1; for (int splineIndex = 0, splineCount = m_splines.size(); splineIndex < splineCount; ++splineIndex) { ISplineInterpolator* pSpline = m_splines[splineIndex].pSpline; // Find the range of keys to process. int keyCount = (pSpline ? pSpline->GetKeyCount() : 0); int firstKeyIndex = keyCount; int lastKeyIndex = -1; for (int i = 0; i < keyCount; ++i) { if (pSpline->IsKeySelectedAtAnyDimension(i)) { firstKeyIndex = min(firstKeyIndex, i); lastKeyIndex = max(lastKeyIndex, i); } } // Find the parameters of a line between the start and end points. This will form the centre line // around which the amplitude of the keys will be scaled. float rangeStartTime = (firstKeyIndex >= 0 && pSpline ? pSpline->GetKeyTime(firstKeyIndex) : 0.0f); float rangeEndTime = (lastKeyIndex >= 0 && pSpline ? pSpline->GetKeyTime(lastKeyIndex) : 0.0f); float rangeLength = max(0.01f, rangeEndTime - rangeStartTime); for (int nCurrentDimension = 0; nCurrentDimension < pSpline->GetNumDimensions(); nCurrentDimension++) { ISplineInterpolator::ValueType afRangeStartValue; if (firstKeyIndex >= 0 && pSpline) { pSpline->GetKeyValue(firstKeyIndex, afRangeStartValue); } else { memset(afRangeStartValue, 0, sizeof(ISplineInterpolator::ValueType)); } ISplineInterpolator::ValueType afRangeEndValue; if (lastKeyIndex >= 0 && pSpline) { pSpline->GetKeyValue(lastKeyIndex, afRangeEndValue); } else { memset(afRangeEndValue, 0, sizeof(ISplineInterpolator::ValueType)); } float centreM = (afRangeEndValue[nCurrentDimension] - afRangeStartValue[nCurrentDimension]) / rangeLength; float centreC = afRangeStartValue[nCurrentDimension] - centreM * rangeStartTime; // Calculate the scale factor, based on how the mouse was dragged. float dragCentreValue = centreM * time + centreC; float dragCentreOffset = startValue - dragCentreValue; float offsetScale = (fabs(dragCentreOffset) > 0.001 ? (offset + dragCentreOffset) / dragCentreOffset : 1.0f); // Scale all the selected keys around this central line. for (int i = 0; i < keyCount; ++i) { if (pSpline->IsKeySelectedAtDimension(i, nCurrentDimension)) { float keyTime = (pSpline ? pSpline->GetKeyTime(i) : 0.0f); float centreValue = keyTime * centreM + centreC; ISplineInterpolator::ValueType afKeyValue; if (pSpline) { pSpline->GetKeyValue(i, afKeyValue); } else { memset(afKeyValue, 0, sizeof(ISplineInterpolator::ValueType)); } float keyOffset = afKeyValue[nCurrentDimension] - centreValue; float newKeyOffset = keyOffset * offsetScale; if (pSpline) { afKeyValue[nCurrentDimension] = centreValue + newKeyOffset; pSpline->SetKeyValue(i, afKeyValue); } } } } } update(); if (m_pTimelineCtrl) { m_pTimelineCtrl->update(); } SendNotifyEvent(SPLN_CHANGE); } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::TimeScaleKeys(float time, float startTime, float endTime) { // Calculate the scaling parameters (ie t1 = t0 * M + C). float timeScaleM = 1.0f; if (fabsf(startTime - time) > MIN_TIME_EPSILON_FOR_SCALING) { timeScaleM = (endTime - time) / (startTime - time); } float timeScaleC = endTime - startTime * timeScaleM; // Loop through all keys that are selected. m_pHitSpline = 0; m_pHitDetailSpline = 0; m_nHitKeyIndex = -1; float affectedRangeMin = FLT_MAX; float affectedRangeMax = -FLT_MAX; for (int splineIndex = 0, splineCount = m_splines.size(); splineIndex < splineCount; ++splineIndex) { ISplineInterpolator* pSpline = m_splines[splineIndex].pSpline; int keyCount = pSpline->GetKeyCount(); float keyRangeMin = FLT_MAX; float keyRangeMax = -FLT_MAX; for (int i = 0; i < keyCount; i++) { if (pSpline->IsKeySelectedAtAnyDimension(i)) { float oldTime = pSpline->GetKeyTime(i); float t = SnapTime(oldTime * timeScaleM + timeScaleC); pSpline->SetKeyTime(i, SnapTimeToGridVertical(t)); keyRangeMin = min(keyRangeMin, oldTime); keyRangeMin = min(keyRangeMin, t); keyRangeMax = max(keyRangeMax, oldTime); keyRangeMax = max(keyRangeMax, t); } } if (keyRangeMin <= keyRangeMax) { // Changes to a key's value affect spline up to two keys away. int lastMovedKey = 0; for (int keyIndex = 0; keyIndex < keyCount; ++keyIndex) { if (pSpline->GetKeyTime(keyIndex) <= keyRangeMax) { lastMovedKey = keyIndex + 1; } } int firstMovedKey = pSpline->GetKeyCount(); for (int keyIndex = pSpline->GetKeyCount() - 1; keyIndex >= 0; --keyIndex) { if (pSpline->GetKeyTime(keyIndex) >= keyRangeMin) { firstMovedKey = keyIndex; } } int firstAffectedKey = max(0, firstMovedKey - 2); int lastAffectedKey = min(keyCount - 1, lastMovedKey + 2); affectedRangeMin = min(affectedRangeMin, (firstAffectedKey <= 0 ? m_timeRange.start : pSpline->GetKeyTime(firstAffectedKey))); affectedRangeMax = max(affectedRangeMax, (lastAffectedKey >= keyCount - 1 ? m_timeRange.end : pSpline->GetKeyTime(lastAffectedKey))); // Loop through all moved keys, checking whether there are multiple keys on the same frame. float lastKeyTime = -FLT_MAX; pSpline->Update(); for (int keyIndex = 0, keys = pSpline->GetKeyCount(); keyIndex <= keys; ) { float keyTime = pSpline->GetKeyTime(keyIndex); if (fabsf(keyTime - lastKeyTime) < m_fMinTimeEpsilon) { --keys; pSpline->RemoveKey(keyIndex); } else { ++keyIndex; lastKeyTime = keyTime; } } } } int rangeMin = TimeToXOfs(affectedRangeMin); int rangeMax = TimeToXOfs(affectedRangeMax); if (m_timeRange.start == affectedRangeMin) { rangeMin = m_rcSpline.left(); } if (m_timeRange.end == affectedRangeMax) { rangeMax = m_rcSpline.right(); } QRect invalidRect(QPoint(rangeMin - 3, m_rcSpline.top()), QPoint(rangeMax + 3, m_rcSpline.bottom() + 1) - QPoint(1, 1)); update(invalidRect); if (m_pTimelineCtrl) { m_pTimelineCtrl->update(); } m_bKeyTimesDirty = true; SendNotifyEvent(SPLN_CHANGE); } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::ValueScaleKeys(float startValue, float endValue) { // Calculate the scaling parameters. float valueScale = 1.0f; if (fabsf(startValue) > MIN_TIME_EPSILON_FOR_SCALING) { valueScale = endValue / startValue; } // Loop through all keys that are selected. m_pHitSpline = 0; m_pHitDetailSpline = 0; m_nHitKeyIndex = -1; m_nHitDimension = -1; for (int splineIndex = 0, splineCount = m_splines.size(); splineIndex < splineCount; ++splineIndex) { ISplineInterpolator* pSpline = m_splines[splineIndex].pSpline; int keyCount = pSpline->GetKeyCount(); for (int i = 0; i < keyCount; i++) { for (int nCurrentDimension = 0; nCurrentDimension < pSpline->GetNumDimensions(); nCurrentDimension++) { if (pSpline->IsKeySelectedAtDimension(i, nCurrentDimension)) { ISplineInterpolator::ValueType afValue; pSpline->GetKeyValue(i, afValue); afValue[nCurrentDimension] = SnapValue(afValue[nCurrentDimension] * valueScale); pSpline->SetKeyValue(i, afValue); } } } } update(); SendNotifyEvent(SPLN_CHANGE); } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::MoveSelectedKeys(Vec2 offset, bool copyKeys) { m_pHitSpline = 0; m_pHitDetailSpline = 0; m_nHitKeyIndex = -1; m_nHitDimension = -1; if (copyKeys) { DuplicateSelectedKeys(); } // For each spline... for (int splineIndex = 0, splineCount = m_splines.size(); splineIndex < splineCount; ++splineIndex) { ISplineInterpolator* pSpline = m_splines[splineIndex].pSpline; int keyCount = pSpline->GetKeyCount(); for (int i = 0; i < keyCount; i++) { float oldTime = pSpline->GetKeyTime(i); float t = SnapTime(oldTime + offset.x); if (pSpline->IsKeySelectedAtAnyDimension(i)) { if (pSpline->FindKey(t, m_fMinTimeEpsilon) < 0) { pSpline->SetKeyTime(i, SnapTimeToGridVertical(t)); } } for (int nCurrentDimension = 0; nCurrentDimension < pSpline->GetNumDimensions(); nCurrentDimension++) { if (pSpline->IsKeySelectedAtDimension(i, nCurrentDimension)) { ISplineInterpolator::ValueType afValue; pSpline->GetKeyValue(i, afValue); afValue[nCurrentDimension] = SnapValue(afValue[nCurrentDimension] + offset.y); pSpline->SetKeyValue(i, afValue); } } } } update(); if (m_pTimelineCtrl) { m_pTimelineCtrl->update(); } m_bKeyTimesDirty = true; SendNotifyEvent(SPLN_CHANGE); } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::RemoveKey(ISplineInterpolator* pSpline, int nKey) { CUndo undo("Remove Spline Key"); ConditionalStoreUndo(); m_bKeyTimesDirty = true; SendNotifyEvent(SPLN_BEFORE_CHANGE); m_pHitSpline = 0; m_pHitDetailSpline = 0; m_nHitKeyIndex = -1; if (nKey != -1) { pSpline->RemoveKey(nKey); } SendNotifyEvent(SPLN_CHANGE); update(); } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::RemoveSelectedKeys() { CUndo undo("Remove Spline Key"); StoreUndo(); SendNotifyEvent(SPLN_BEFORE_CHANGE); m_pHitSpline = 0; m_pHitDetailSpline = 0; m_nHitKeyIndex = -1; for (int splineIndex = 0, splineCount = m_splines.size(); splineIndex < splineCount; ++splineIndex) { ISplineInterpolator* pSpline = m_splines[splineIndex].pSpline; for (int i = 0; i < (int)pSpline->GetKeyCount(); ) { if (pSpline->IsKeySelectedAtAnyDimension(i)) { pSpline->RemoveKey(i); } else { i++; } } } m_bKeyTimesDirty = true; SendNotifyEvent(SPLN_CHANGE); update(); } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::RemoveSelectedKeyTimesImpl() { int numSelectedKeyTimes = 0; for (std::vector::iterator it = m_keyTimes.begin(), end = m_keyTimes.end(); it != end; ++it) { if ((*it).selected) { ++numSelectedKeyTimes; } } if (numSelectedKeyTimes) { CUndo undo("Remove Spline Key"); StoreUndo(); SendNotifyEvent(SPLN_BEFORE_CHANGE); for (int splineIndex = 0, end = m_splines.size(); splineIndex < end; ++splineIndex) { std::vector::iterator itTime = m_keyTimes.begin(), endTime = m_keyTimes.end(); for (int keyIndex = 0, endIndex = m_splines[splineIndex].pSpline->GetKeyCount(); keyIndex < endIndex; ) { const float threshold = 0.01f; for (; itTime != endTime && (*itTime).time < m_splines[splineIndex].pSpline->GetKeyTime(keyIndex) - threshold; ++itTime) { ; } if (itTime != endTime && fabsf((*itTime).time - m_splines[splineIndex].pSpline->GetKeyTime(keyIndex)) < threshold && (*itTime).selected) { m_splines[splineIndex].pSpline->RemoveKey(keyIndex); } else { ++keyIndex; } } } } } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::RemoveSelectedKeyTimes() { RemoveSelectedKeyTimesImpl(); m_bKeyTimesDirty = true; SendNotifyEvent(SPLN_CHANGE); update(); } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::RedrawWindowAroundMarker() { UpdateKeyTimes(); std::vector::iterator itKeyTime = std::lower_bound(m_keyTimes.begin(), m_keyTimes.end(), KeyTime(m_fTimeMarker, 0)); int keyTimeIndex = (itKeyTime != m_keyTimes.end() ? itKeyTime - m_keyTimes.begin() : m_keyTimes.size()); int redrawRangeStart = (keyTimeIndex >= 2 ? TimeToXOfs(m_keyTimes[keyTimeIndex - 2].time) : m_rcSpline.left()); int redrawRangeEnd = (keyTimeIndex < int(m_keyTimes.size()) - 2 ? TimeToXOfs(m_keyTimes[keyTimeIndex + 2].time) : m_rcSpline.right() + 1); QRect rc(QPoint(redrawRangeStart, m_rcSpline.top()), QPoint(redrawRangeEnd, m_rcSpline.bottom() + 1) - QPoint(1, 1)); rc = rc.normalized().intersected(m_rcSpline); m_TimeUpdateRect = QRect(QPoint(1, 2), QSize(2, 2)); update(rc); } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::SplinesChanged() { m_bKeyTimesDirty = true; UpdateKeyTimes(); update(); } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::SetControlAmplitude(bool controlAmplitude) { m_controlAmplitude = controlAmplitude; } ////////////////////////////////////////////////////////////////////////// bool AbstractSplineWidget::GetControlAmplitude() const { return m_controlAmplitude; } float AbstractSplineWidget::SnapTimeToGridVertical(float time) { //float fSnapTime = int((time * m_fGridTimeScale) + 0.5f) * (1.0f / m_fGridTimeScale); float fSnapTime = time; return fSnapTime; } ////////////////////////////////////////////////////////////////////////// int AbstractSplineWidget::InsertKey(ISplineInterpolator* pSpline, ISplineInterpolator* pDetailSpline, const QPoint& point) { CUndo undo("Spline Insert Key"); StoreUndo(); float time, val; PointToTimeValue(point, time, val); time = SnapTimeToGridVertical(time); int i; for (i = 0; i < pSpline->GetKeyCount(); i++) { // Skip if any key already have time that is very close. if (fabs(pSpline->GetKeyTime(i) - time) < m_fMinTimeEpsilon) { return i; } } SendNotifyEvent(SPLN_BEFORE_CHANGE); // The proper key value for a spline that has a detail spline is not what is shown in the control - we have // to remove the detail value to get back to the underlying spline value. if (pDetailSpline) { float offset = 0.0f; pDetailSpline->InterpolateFloat(time, offset); val -= offset; } ClearSelection(); ISplineInterpolator::ValueType currValue; ISplineInterpolator::ZeroValue(currValue); pSpline->Interpolate(time, currValue); if (pSpline->GetNumDimensions() > 1) { } int nKey = pSpline->InsertKey(time, currValue); // TODO: Don't use FE specific snapping! if (m_defaultKeyTangentType != SPLINE_KEY_TANGENT_NONE) { SetKeyTangentType(pSpline, nKey, m_defaultKeyTangentType); } //int nKey = pSpline->InsertKeyFloat( time,val ); // TODO: Don't use FE specific snapping! SelectKey(pSpline, nKey, 0, true); update(); m_bKeyTimesDirty = true; SendNotifyEvent(SPLN_CHANGE); return nKey; } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::ClearSelection() { ConditionalStoreUndo(); for (int splineIndex = 0, splineCount = m_splines.size(); splineIndex < splineCount; ++splineIndex) { ISplineInterpolator* pSpline = m_splines[splineIndex].pSpline; for (int i = 0; i < (int)pSpline->GetKeyCount(); i++) { pSpline->SelectKeyAllDimensions(i, false); } } ClearSelectedKeys(); } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::SetTimeMarker(float fTime) { if (m_pTimelineCtrl) { m_pTimelineCtrl->SetTimeMarker(fTime); } if (fTime == m_fTimeMarker) { return; } //// Erase old first. //float x1 = TimeToXOfs(m_fTimeMarker); //float x2 = TimeToXOfs(fTime); //QRect rc(QPoint(x1, m_rcSpline.top()), QPoint(x2, m_rcSpline.bottom())); //rc = rc.normalized().adjusted(-3, 0, 3, 0);// .intersected(m_rcSpline); m_TimeUpdateRect = {}; m_fTimeMarker = fTime; update(); } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::StoreUndo() { if (CUndo::IsRecording() && !m_pCurrentUndo) { std::vector splines(m_splines.size()); for (int splineIndex = 0, splineCount = m_splines.size(); splineIndex < splineCount; ++splineIndex) { splines[splineIndex] = m_splines[splineIndex].pSpline; } CUndo::Record(m_pCurrentUndo = CreateSplineCtrlUndoObject(splines)); } } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::ConditionalStoreUndo() { if (m_editMode == TrackingMode || m_editMode == SelectMode) { StoreUndo(); } } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::ClearSelectedKeys() { for (std::vector::iterator it = m_keyTimes.begin(), end = m_keyTimes.end(); it != end; ++it) { (*it).selected = false; } } ////////////////////////////////////////////////////////////////////////// class CKeyCopyInfo { public: ISplineInterpolator::ValueType value; float time; int flags; ISplineInterpolator::ValueType tin, tout; }; void AbstractSplineWidget::DuplicateSelectedKeys() { m_pHitSpline = 0; m_pHitDetailSpline = 0; m_nHitKeyIndex = -1; typedef std::vector KeysToAddContainer; KeysToAddContainer keysToInsert; for (int splineIndex = 0, splineCount = m_splines.size(); splineIndex < splineCount; ++splineIndex) { ISplineInterpolator* pSpline = m_splines[splineIndex].pSpline; keysToInsert.resize(0); for (int i = 0; i < pSpline->GetKeyCount(); i++) { // In this particular case, the dimension doesn't matter. if (pSpline->IsKeySelectedAtAnyDimension(i)) { keysToInsert.resize(keysToInsert.size() + 1); CKeyCopyInfo& copyInfo = keysToInsert.back(); copyInfo.time = pSpline->GetKeyTime(i); pSpline->GetKeyValue(i, copyInfo.value); pSpline->GetKeyTangents(i, copyInfo.tin, copyInfo.tout); copyInfo.flags = pSpline->GetKeyFlags(i); } } for (KeysToAddContainer::iterator keyToAdd = keysToInsert.begin(), endKeysToAdd = keysToInsert.end(); keyToAdd != endKeysToAdd; ++keyToAdd) { int keyIndex = pSpline->InsertKey(SnapTimeToGridVertical((*keyToAdd).time), (*keyToAdd).value); pSpline->SetKeyTangents(keyIndex, (*keyToAdd).tin, (*keyToAdd).tout); pSpline->SetKeyFlags(keyIndex, (*keyToAdd).flags & (~ESPLINE_KEY_UI_SELECTED_MASK)); } } m_bKeyTimesDirty = true; } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::ZeroAll() { GetIEditor()->BeginUndo(); typedef std::vector SplineContainer; SplineContainer splines; for (int splineIndex = 0; splineIndex < int(m_splines.size()); ++splineIndex) { ISplineInterpolator* pSpline = m_splines[splineIndex].pSpline; int keyIndex = (pSpline ? pSpline->FindKey(m_fTimeMarker, 0.015f) : -1); if (pSpline && keyIndex >= 0) { splines.push_back(pSpline); } } CUndo::Record(CreateSplineCtrlUndoObject(splines)); for (SplineContainer::iterator itSpline = splines.begin(); itSpline != splines.end(); ++itSpline) { int keyIndex = ((*itSpline) ? (*itSpline)->FindKey(m_fTimeMarker, 0.015f) : -1); if ((*itSpline) && keyIndex >= 0) { (*itSpline)->SetKeyValueFloat(keyIndex, 0.0f); } } GetIEditor()->AcceptUndo("Zero All"); m_pCurrentUndo = nullptr; } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::KeyAll() { GetIEditor()->BeginUndo(); typedef std::vector SplineContainer; SplineContainer splines; for (int splineIndex = 0; splineIndex < int(m_splines.size()); ++splineIndex) { ISplineInterpolator* pSpline = m_splines[splineIndex].pSpline; int keyIndex = (pSpline ? pSpline->FindKey(m_fTimeMarker, 0.015f) : -1); if (pSpline && keyIndex == -1) { splines.push_back(pSpline); } } CUndo::Record(CreateSplineCtrlUndoObject(splines)); for (SplineContainer::iterator itSpline = splines.begin(); itSpline != splines.end(); ++itSpline) { float value = 0.0f; (*itSpline)->InterpolateFloat(m_fTimeMarker, value); int keyIndex = (*itSpline)->InsertKeyFloat(SnapTimeToGridVertical(m_fTimeMarker), value); if (m_defaultKeyTangentType != SPLINE_KEY_TANGENT_NONE) { SetKeyTangentType(*itSpline, keyIndex, m_defaultKeyTangentType); } } GetIEditor()->AcceptUndo("Key All"); m_pCurrentUndo = nullptr; } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::SelectAll() { for (int splineIndex = 0, splineCount = m_splines.size(); splineIndex < splineCount; ++splineIndex) { ISplineInterpolator* pSpline = m_splines[splineIndex].pSpline; ISplineInterpolator* pDetailSpline = m_splines[splineIndex].pDetailSpline; for (int i = 0; i < (int)pSpline->GetKeyCount(); i++) { pSpline->SelectKeyAllDimensions(i, true); } } update(); } ////////////////////////////////////////////////////////////////////////// void SplineWidget::SendNotifyEvent(int nEvent) { if (nEvent == SPLN_BEFORE_CHANGE) { ConditionalStoreUndo(); } switch (nEvent) { case SPLN_BEFORE_CHANGE: Q_EMIT beforeChange(); break; case SPLN_CHANGE: Q_EMIT change(); break; case SPLN_TIME_CHANGE: Q_EMIT timeChange(); break; case SPLN_SCROLL_ZOOM: Q_EMIT scrollZoomRequested(); break; case SPLN_KEY_SELECTION_CHANGE: Q_EMIT keySelectionChange(); break; case NM_CLICK: Q_EMIT clicked(); break; case NM_RCLICK: Q_EMIT rightClicked(); break; } } ////////////////////////////////////////////////////////////////////////// void SplineWidget::SetTimelineCtrl(TimelineWidget* pTimelineCtrl) { m_pTimelineCtrl = pTimelineCtrl; if (m_pTimelineCtrl) { QWidget* pOwner = pTimelineCtrl->parentWidget(); pTimelineCtrl->setParent(this); pTimelineCtrl->SetZoom(m_grid.zoom.x); pTimelineCtrl->SetOrigin(m_grid.origin.x); pTimelineCtrl->SetKeyTimeSet(this); pTimelineCtrl->update(); } } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::AddSpline(ISplineInterpolator* pSpline, ISplineInterpolator* pDetailSpline, const QColor& color) { 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] = color; } si.pSpline = pSpline; si.pDetailSpline = pDetailSpline; m_splines.push_back(si); m_bKeyTimesDirty = true; update(); } void AbstractSplineWidget::AddSpline(ISplineInterpolator* pSpline, ISplineInterpolator* pDetailSpline, 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 = pDetailSpline; m_splines.push_back(si); m_bKeyTimesDirty = true; update(); } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::RemoveSpline(ISplineInterpolator* pSpline) { for (int i = 0; i < (int)m_splines.size(); i++) { if (m_splines[i].pSpline == pSpline) { m_splines.erase(m_splines.begin() + i); return; } } m_bKeyTimesDirty = true; update(); } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::RemoveAllSplines() { m_splines.clear(); m_bKeyTimesDirty = true; update(); } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::SelectRectangle(const QRect& rc, bool bSelect) { ConditionalStoreUndo(); ClearSelectedKeys(); Vec2 vec0 = ClientToWorld(rc.topLeft()); Vec2 vec1 = ClientToWorld(rc.bottomRight()); float t0 = vec0.x; float t1 = vec1.x; float v0 = vec0.y; float v1 = vec1.y; if (v0 > v1) { std::swap(v0, v1); } if (t0 > t1) { std::swap(t0, t1); } for (int splineIndex = 0, splineCount = m_splines.size(); splineIndex < splineCount; ++splineIndex) { ISplineInterpolator* pSpline = m_splines[splineIndex].pSpline; ISplineInterpolator* pDetailSpline = m_splines[splineIndex].pDetailSpline; for (int i = 0; i < (int)pSpline->GetKeyCount(); i++) { float t = pSpline->GetKeyTime(i); ISplineInterpolator::ValueType afValue; pSpline->GetKeyValue(i, afValue); int nTotalNumberOfDimensions(pSpline->GetNumDimensions()); ISplineInterpolator::ValueType afDetailValue; if (pDetailSpline) { ISplineInterpolator::ZeroValue(afDetailValue); pDetailSpline->Interpolate(t, afDetailValue); } for (int nCurrentDimension = 0; nCurrentDimension < nTotalNumberOfDimensions; nCurrentDimension++) { if (pDetailSpline) { afValue[nCurrentDimension] = afValue[nCurrentDimension] + afDetailValue[nCurrentDimension]; } if (t >= t0 && t <= t1 && afValue[nCurrentDimension] >= v0 && afValue[nCurrentDimension] <= v1) { pSpline->SelectKeyAtDimension(i, nCurrentDimension, bSelect); } } } } SendNotifyEvent(SPLN_CHANGE); } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::CopyKeys() { // Copy selected keys. if (m_splines.empty() || GetNumSelected() == 0) { return; } XmlNodeRef rootNode = XmlHelpers::CreateXmlNode("SplineKeys"); int i; float minTime = FLT_MAX; float maxTime = -FLT_MAX; ISplineInterpolator* pSpline = m_splines[0].pSpline; for (i = 0; i < (int)pSpline->GetKeyCount(); i++) { if (!pSpline->IsKeySelectedAtAnyDimension(i)) { continue; } float t = pSpline->GetKeyTime(i); if (t < minTime) { minTime = t; } if (t > maxTime) { maxTime = t; } } rootNode->setAttr("start", minTime); rootNode->setAttr("end", maxTime); for (i = 0; i < (int)pSpline->GetKeyCount(); i++) { if (!pSpline->IsKeySelectedAtAnyDimension(i)) { continue; } float t = pSpline->GetKeyTime(i); // Store offset time from copy/paste range. ISplineInterpolator::ValueType afValue; pSpline->GetKeyValue(i, afValue); float tin, tout; ISplineInterpolator::ValueType vtin, vtout; pSpline->GetKeyTangents(i, vtin, vtout); tin = vtin[0]; tout = vtout[0]; XmlNodeRef keyNode = rootNode->newChild("Key"); keyNode->setAttr("time", t); keyNode->setAttr("flags", (int)pSpline->GetKeyFlags(i)); keyNode->setAttr("in", tin); keyNode->setAttr("out", tout); for (int i = 0; i < pSpline->GetNumDimensions(); ++i) { XmlNodeRef dimensionNode = keyNode->newChild("values"); dimensionNode->setAttr("value", afValue[i]); } } CClipboard clipboard(WidgetCast()); clipboard.Put(rootNode); } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::PasteKeys() { if (m_splines.empty() || GetNumSelected() == 0) { return; } ISplineInterpolator* pSpline = m_splines[0].pSpline; CClipboard clipboard(WidgetCast()); if (clipboard.IsEmpty()) { return; } XmlNodeRef rootNode = clipboard.Get(); if (!rootNode) { return; } if (!rootNode->isTag("SplineKeys")) { return; } float minTime = 0; float maxTime = 0; rootNode->getAttr("start", minTime); rootNode->getAttr("end", maxTime); const QPoint point = mapFromGlobal(QCursor::pos()); float fTime = XOfsToTime(point.x()); float fTimeRange = (maxTime - minTime); CUndo undo("Paste Spline Keys"); ConditionalStoreUndo(); ClearSelection(); int i; // Delete keys in range min to max time. for (i = 0; i < pSpline->GetKeyCount(); ) { float t = pSpline->GetKeyTime(i); if (t >= fTime && t <= fTime + fTimeRange) { pSpline->RemoveKey(i); } else { i++; } } for (i = 0; i < rootNode->getChildCount(); i++) { XmlNodeRef keyNode = rootNode->getChild(i); float t = 0; float tin = 0; float tout = 0; int flags = 0; keyNode->getAttr("time", t); keyNode->getAttr("flags", flags); keyNode->getAttr("in", tin); keyNode->getAttr("out", tout); int nNumberOfChildXMLNodes(0); int nCurrentChildXMLNode(0); int nCurrentValue(0); ISplineInterpolator::ValueType afValue; nNumberOfChildXMLNodes = keyNode->getChildCount(); for (nCurrentChildXMLNode = 0; nCurrentChildXMLNode < nNumberOfChildXMLNodes; ++nCurrentChildXMLNode) { XmlNodeRef rSubKeyNode = keyNode->getChild(nCurrentChildXMLNode); if (rSubKeyNode->isTag("values")) { rSubKeyNode->getAttr("value", afValue[nCurrentValue]); nCurrentValue++; } } int key = pSpline->InsertKey(SnapTimeToGridVertical(t - minTime + fTime), afValue); if (key >= 0) { pSpline->SelectKeyAllDimensions(key, true); ISplineInterpolator::ValueType vtin, vtout; vtin[0] = tin; vtout[0] = tout; pSpline->SetKeyTangents(key, vtin, vtout); } } m_bKeyTimesDirty = true; update(); } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::ModifySelectedKeysFlags(int nRemoveFlags, int nAddFlags) { CUndo undo("Modify Spline Keys"); StoreUndo(); SendNotifyEvent(SPLN_BEFORE_CHANGE); for (int splineIndex = 0, splineCount = m_splines.size(); splineIndex < splineCount; ++splineIndex) { ISplineInterpolator* pSpline = m_splines[splineIndex].pSpline; 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); flags &= ~nRemoveFlags; flags |= nAddFlags; pSpline->SetKeyFlags(i, flags); break; } } } } SendNotifyEvent(SPLN_CHANGE); update(); } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::FitSplineToViewWidth() { // Calculate time zoom so that whole time range fits. float t0 = FLT_MAX; float t1 = -FLT_MAX; bool bAnyKey = false; for (int i = 0; i < int(m_splines.size()); ++i) { ////////////////////////////////////////////////////////////////////////// ISplineInterpolator* pSpline = m_splines[i].pSpline; if (!pSpline) { continue; } for (int keyIndex = 0; pSpline && keyIndex < pSpline->GetKeyCount(); ++keyIndex) { float keyTime = pSpline->GetKeyTime(keyIndex); t0 = std::min(t0, keyTime); t1 = std::max(t1, keyTime); bAnyKey = true; } } if (!bAnyKey) { t0 = m_timeRange.start; t1 = m_timeRange.end; } float zoom = abs(m_rcSpline.width() - 20) / max(1.0f, fabs(t1 - t0)); SetZoom(Vec2(zoom, m_grid.zoom.y)); SetScrollOffset(Vec2(t0, m_grid.origin.y)); } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::FitSplineToViewHeight() { // Calculate time zoom so that whole value range fits. Range splineRange = GetSplinesRange(); if (splineRange.start == FLT_MAX) { splineRange = m_valueRange; } float zoom = abs(m_rcSpline.height() - 40) / max(minViewRange, splineRange.Length()); SetZoom(Vec2(m_grid.zoom.x, zoom)); // Center the range if it's less than the minRange by adjusting it's offset. float scrollOffset = max(0.0f, minViewRange - splineRange.Length()) / 2.0f; SetScrollOffset(Vec2(m_grid.origin.x, splineRange.start - scrollOffset)); } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::FitSplineHeightToValueRange() { Range splineRange = GetSplinesRange(); splineRange.start = min(splineRange.start, m_valueRange.start); splineRange.end = max(splineRange.end, m_valueRange.end); float zoom = abs(m_rcSpline.height() - 40) / max(minViewRange, splineRange.Length()); SetZoom(Vec2(m_grid.zoom.x, zoom)); SetScrollOffset(Vec2(m_grid.origin.x, splineRange.start)); } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::OnUserCommand(UINT cmd) { switch (cmd) { case ID_TANGENT_IN_ZERO: ModifySelectedKeysFlags(SPLINE_KEY_TANGENT_IN_MASK, SPLINE_KEY_TANGENT_ZERO << SPLINE_KEY_TANGENT_IN_SHIFT); break; case ID_TANGENT_IN_STEP: ModifySelectedKeysFlags(SPLINE_KEY_TANGENT_IN_MASK, SPLINE_KEY_TANGENT_STEP << SPLINE_KEY_TANGENT_IN_SHIFT); break; case ID_TANGENT_IN_LINEAR: ModifySelectedKeysFlags(SPLINE_KEY_TANGENT_IN_MASK, SPLINE_KEY_TANGENT_LINEAR << SPLINE_KEY_TANGENT_IN_SHIFT); break; case ID_TANGENT_OUT_ZERO: ModifySelectedKeysFlags(SPLINE_KEY_TANGENT_OUT_MASK, SPLINE_KEY_TANGENT_ZERO << SPLINE_KEY_TANGENT_OUT_SHIFT); break; case ID_TANGENT_OUT_STEP: ModifySelectedKeysFlags(SPLINE_KEY_TANGENT_OUT_MASK, SPLINE_KEY_TANGENT_STEP << SPLINE_KEY_TANGENT_OUT_SHIFT); break; case ID_TANGENT_OUT_LINEAR: ModifySelectedKeysFlags(SPLINE_KEY_TANGENT_OUT_MASK, SPLINE_KEY_TANGENT_LINEAR << SPLINE_KEY_TANGENT_OUT_SHIFT); break; case ID_TANGENT_AUTO: ModifySelectedKeysFlags(SPLINE_KEY_TANGENT_IN_MASK | SPLINE_KEY_TANGENT_OUT_MASK, 0); break; case ID_SPLINE_FIT_X: FitSplineToViewWidth(); break; case ID_SPLINE_FIT_Y: FitSplineToViewHeight(); break; case ID_SPLINE_SNAP_GRID_X: SetSnapTime(!m_bSnapTime); break; case ID_SPLINE_SNAP_GRID_Y: SetSnapValue(!m_bSnapValue); break; case ID_SPLINE_PREVIOUS_KEY: GotoNextKey(true); break; case ID_SPLINE_NEXT_KEY: GotoNextKey(false); break; case ID_SPLINE_FLATTEN_ALL: RemoveAllKeysButThis(); break; } } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::GotoNextKey(bool previousKey) { if (GetNumSelected() == 1) { bool boFoundTheSelectedKey(false); for (int splineIndex = 0, endSpline = m_splines.size(); splineIndex < endSpline; ++splineIndex) { ISplineInterpolator* pSpline = m_splines[splineIndex].pSpline; for (int i = 0; i < pSpline->GetKeyCount(); i++) { for (int nCurrentDimension = 0; nCurrentDimension < pSpline->GetNumDimensions(); nCurrentDimension++) { if (pSpline->IsKeySelectedAtDimension(i, nCurrentDimension)) { boFoundTheSelectedKey = true; if ((previousKey && i > 0) || (!previousKey && i + 1 < pSpline->GetKeyCount())) { int nextKey = previousKey ? i - 1 : i + 1; float keyTime = pSpline->GetKeyTime(nextKey); SetTimeMarker(keyTime); ISplineInterpolator::ValueType afValue; pSpline->GetKeyValue(nextKey, afValue); pSpline->SelectKeyAtDimension(i, nCurrentDimension, false); pSpline->SelectKeyAtDimension(nextKey, nCurrentDimension, true); // Set the new scrolled coordinates float ofsx = keyTime - ((m_grid.rect.right() + 1) / 2) / m_grid.zoom.x; float ofsy = afValue[nCurrentDimension] - ((m_grid.rect.bottom() + 1) / 2) / m_grid.zoom.y; SetScrollOffset(Vec2(ofsx, ofsy)); } break; } } if (boFoundTheSelectedKey) { break; } } } } else { for (int splineIndex = 0, endSpline = m_splines.size(); splineIndex < endSpline; ++splineIndex) { ISplineInterpolator* pSpline = m_splines[splineIndex].pSpline; float fClosestKeyTime = -1.0f; float fClosestDist = 1E8; for (int i = 0; i < pSpline->GetKeyCount(); i++) { float fKeyTime = pSpline->GetKeyTime(i); float fKeyDist = previousKey ? m_fTimeMarker - fKeyTime : fKeyTime - m_fTimeMarker; if ((fKeyDist > 0.0f) && (fKeyDist < fClosestDist)) { fClosestDist = fKeyDist; fClosestKeyTime = pSpline->GetKeyTime(i); } } if (fClosestKeyTime >= 0.0f) { SetTimeMarker(fClosestKeyTime); float averageValue = 0.f; int dimensions = pSpline->GetNumDimensions(); for (int i = 0; i < dimensions; i++) { float keyValue; int keyNum = pSpline->FindKey(fClosestKeyTime); pSpline->GetKeyValueFloat(keyNum, keyValue); averageValue += keyValue; } // Set the new scrolled coordinates float ofsx = fClosestKeyTime - ((m_grid.rect.right() + 1) / 2) / m_grid.zoom.x; float ofsy = averageValue / dimensions - ((m_grid.rect.bottom() + 1) / 2) / m_grid.zoom.y; SetScrollOffset(Vec2(ofsx, ofsy)); } } } SendNotifyEvent(SPLN_TIME_CHANGE); } ////////////////////////////////////////////////////////////////////////// void AbstractSplineWidget::RemoveAllKeysButThis() { std::vector keys; for (int splineIndex = 0, endSpline = m_splines.size(); splineIndex < endSpline; ++splineIndex) { ISplineInterpolator* pSpline = m_splines[splineIndex].pSpline; for (int i = 0; i < pSpline->GetKeyCount(); i++) { if (pSpline->IsKeySelectedAtAnyDimension(i)) { keys.push_back(i); } } for (int i = pSpline->GetKeyCount(); i >= 0; i--) { bool saveKey = false; for (int nIndex = 0; nIndex < keys.size(); nIndex++) { if (keys[nIndex] == i) { saveKey = true; } } if (!saveKey) { RemoveKey(pSpline, i); } } } } ////////////////////////////////////////////////////////////////////////// ISplineCtrlUndo* AbstractSplineWidget::CreateSplineCtrlUndoObject(std::vector& splineContainer) { return new CUndoSplineCtrlEx(this, splineContainer); } Range AbstractSplineWidget::GetSplinesRange() { Range range(FLT_MAX, -FLT_MAX); for (int i = 0; i < int(m_splines.size()); ++i) { ////////////////////////////////////////////////////////////////////////// ISplineInterpolator* pSpline = m_splines[i].pSpline; if (!pSpline) { continue; } ISplineInterpolator::ValueType value; for (int keyIndex = 0; pSpline && keyIndex < pSpline->GetKeyCount(); ++keyIndex) { pSpline->GetKeyValue(keyIndex, value); for (int d = 0, numDim = pSpline->GetNumDimensions(); d < numDim; d++) { range.start = std::min(range.start, value[d]); range.end = std::max(range.end, value[d]); } } } return range; } #include