You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/Editor/Controls/SplineCtrlEx.cpp

3351 lines
99 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "SplineCtrlEx.h"
// Qt
#include <QPainter>
#include <QPainterPath>
#include <QToolTip>
#include <QRubberBand>
// Editor
#include "TimelineCtrl.h"
#include "Clipboard.h"
#include "GridUtils.h"
#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<ISplineInterpolator*>& 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 : nullptr);
entry.id = (pSplineSet ? pSplineSet->GetIDFromSpline(pSpline) : nullptr);
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);
}
}
int GetSize() override { return sizeof(*this); }
QString GetDescription() override { return "UndoSplineCtrlEx"; };
void Undo(bool bUndo) override
{
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();
}
}
void Redo() override
{
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<int> keySelectionFlags;
_smart_ptr<ISplineBackup> undo;
_smart_ptr<ISplineBackup> redo;
string id;
ISplineInterpolator* pSpline;
};
void SerializeSplines(_smart_ptr<ISplineBackup> SplineEntry::* backup, bool bLoading)
{
AbstractSplineWidget* pCtrl = FindControl(m_pCtrl);
ISplineSet* pSplineSet = (pCtrl ? pCtrl->m_pSplineSet : nullptr);
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:
using CSplineCtrls = std::list<AbstractSplineWidget *>;
static AbstractSplineWidget* FindControl(AbstractSplineWidget* pCtrl)
{
if (!pCtrl)
{
return nullptr;
}
auto iter = std::find(s_activeCtrls.begin(), s_activeCtrls.end(), pCtrl);
if (iter == s_activeCtrls.end())
{
return nullptr;
}
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;
bool IsSelectionChanged() const override
{
AbstractSplineWidget* pCtrl = FindControl(m_pCtrl);
ISplineSet* pSplineSet = (pCtrl ? pCtrl->m_pSplineSet : nullptr);
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<SplineEntry> m_splineEntries;
std::vector<float> 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 = nullptr;
m_totalSplineCount = 0;
m_pHitSpline = nullptr;
m_pHitDetailSpline = nullptr;
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 = nullptr;
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<ISplineInterpolator*> 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<KeyChange> 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<KeyChange>::iterator itChange = individualKeyChanges.begin(); itChange != individualKeyChanges.end(); ++itChange)
{
(*itChange).pSpline->SetKeyTime((*itChange).keyIndex, (*itChange).newTime);
}
if (copyKeys)
{
for (std::vector<KeyChange>::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()([[maybe_unused]] 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, [[maybe_unused]] 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<float> selectedKeyTimes;
selectedKeyTimes.reserve(m_keyTimes.size());
for (std::vector<KeyTime>::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<KeyTime>::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<float>::iterator itSelected = selectedKeyTimes.begin(), endSelected = selectedKeyTimes.end();
for (std::vector<KeyTime>::iterator it = m_keyTimes.begin(), end = m_keyTimes.end(); it != end; ++it)
{
const float thisThreshold = 0.01f;
for (; itSelected != endSelected && (*itSelected) < (*it).time - thisThreshold; ++itSelected)
{
;
}
if (itSelected != endSelected && fabsf((*itSelected) - (*it).time) < thisThreshold)
{
(*it).selected = true;
}
}
m_totalSplineCount = (m_pSplineSet ? m_pSplineSet->GetSplineCount() : 0);
m_bKeyTimesDirty = false;
}
//////////////////////////////////////////////////////////////////////////
void SplineWidget::OnLButtonUp([[maybe_unused]] 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<QKeyEvent*>(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([[maybe_unused]] int min, [[maybe_unused]] 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 nullptr;
}
//////////////////////////////////////////////////////////////////////////////
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 = nullptr;
m_pHitDetailSpline = nullptr;
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 splinePt2 = TimeToPoint(pSpline->GetKeyTime(i), pSpline);
if (abs(splinePt2.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 = nullptr;
m_pHitDetailSpline = nullptr;
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 = nullptr;
m_pHitDetailSpline = nullptr;
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 = nullptr;
m_pHitDetailSpline = nullptr;
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 = nullptr;
m_pHitDetailSpline = nullptr;
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 = nullptr;
m_pHitDetailSpline = nullptr;
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 = nullptr;
m_pHitDetailSpline = nullptr;
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<KeyTime>::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<KeyTime>::iterator itTime = m_keyTimes.begin(), endTime = m_keyTimes.end();
for (int keyIndex = 0, endIndex = m_splines[splineIndex].pSpline->GetKeyCount(); keyIndex < endIndex; )
{
const float thisThreshold = 0.01f;
for (; itTime != endTime && (*itTime).time < m_splines[splineIndex].pSpline->GetKeyTime(keyIndex) - thisThreshold; ++itTime)
{
;
}
if (itTime != endTime && fabsf((*itTime).time - m_splines[splineIndex].pSpline->GetKeyTime(keyIndex)) < thisThreshold && (*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<KeyTime>::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<ISplineInterpolator*> 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<KeyTime>::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 = nullptr;
m_pHitDetailSpline = nullptr;
m_nHitKeyIndex = -1;
using KeysToAddContainer = std::vector<CKeyCopyInfo>;
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();
using SplineContainer = std::vector<ISplineInterpolator *>;
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();
using SplineContainer = std::vector<ISplineInterpolator *>;
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;
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)
{
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 ii = 0; ii < pSpline->GetNumDimensions(); ++ii)
{
XmlNodeRef dimensionNode = keyNode->newChild("values");
dimensionNode->setAttr("value", afValue[ii]);
}
}
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<int> 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<ISplineInterpolator*>& 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 <Controls/moc_SplineCtrlEx.cpp>