Files
o3de/Code/Editor/Controls/SplineCtrlEx.cpp
T
Artur K 56904d1799 Cry Legacy: the Lessening (#5961)
* Generic Cleanup

Removals:

* Unused cvars,member variables and defines
* Windows media center edition support routines
* CSystem - removed debug_GetCallStackRaw/GetRootWindowMessageHandler/
UnloadDLL/ShutdownModuleLibraries

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* Fix CryMessageBox return values in Windows build

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* Remove named selection group and a few smaller unused functionalities

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* Remove ObjectManager export functionality + 2 deprecated functions

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* Remove object legacy freeze/hide support from ObjectManager

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* Remove ObjectManager duplicate name dection, as well as object renaming

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* Remove ObjectManager serialization and selection callbacks

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* ObjectManager - remove MoveObjects/HitTestObject/EndEditParams and related members/functions

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* Remove a bunch of unused clone related functionality + misc

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* ObjectManager - misc removals

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* ObjectManager - more removals

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* Remove unused object selection/retrieval in ObjectManager and LoadObjects in ObjectLoader

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* CBaseObject - remove unused material layers mask support

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* More CBaseObject cleanups.

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* CBaseObject - remove SubObj functions and IMouseCreateCallback

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* Remove unused procedural floor management, helper scale and tags.

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* CBaseObject - remove more unused methods.

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* Removals in multiple places.

CBaseObject - unused `OBJTYPE_DUMMY` flag removed, member `IsSameClass`
CObjectArchive removed unused methods/members
CObjectManager removed unused `Update` method

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* More removals

IDisplayViewport - HitTestLine/GetGridStep/setHitcontext/
GetConstructionPlane
Unused Cry_Matrix44 template specializations.

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* O3DE.exe Project-Centric "Open Editor" fix (#5852)

* The O3DE.exe Open Editor button now attempts to open the Editor in the
build directory of the project being opened.

If their is no Editor within the build directory of the Project, it uses
the Editor.exe in the current O3DE.exe executable directory if it exists

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Engine .gitignore now ignores the build directory if placed in the
AutomatedTesting project

Previously it was just ignoring a `[Bb]uild` directory if it was
directly within the engine root.
This change matches the behavior of the project templates.

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Renamed the ProjectUtils GetEditorDirectory function to
GetEditorExecutablePath

Added a platform specific implementation for retrieving the path to the
Editor executable in the GetEditorExectuablePath function.

It first attempts to locate the Editor via checking the project build
directory for an Editor executable before falling back to checking the
binary directory of the currently running O3DE executable.

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Correct the MacOS GetEditorExecutablePath to return the Editor path

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>
Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* Adding missing C++20 std::erase implementations (#5735)

There were already implementations for std::erase_if.
This adds the counterpart AZStd::erase versions

resolves #5734

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>
Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* More removals and refactors

`ShowMessage` and `CryMessageBox` return void now
Simplify code in `CSystem::WarningV`
Remove unused `CryGetTicksPerSec`
Remove unused WinBase functionality
Replace `nanosleep` call in WinBase with `std::this_thread::sleep_for`
Remove unused Win32Wrapper routines
Remove unused IFunctorBase.h and IEntityObjectListener.h
Fix VectorAndArray.cpp compilation
Use QMessageBox instead of CryMessageBox in the editor.
Remove empty ArchiveVars platform specific files

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* Fix test code.

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* Remove an unused  function

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* CBaseObject and undo description removals.

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* A bunch of removals

* Remove IRenderNode
* Remove editor's KDTree
* Remove StatObjBus, InstanceStatObjEventBus::ReleaseData is redundant (
same functionality in the only available handler is triggered by
`OnCryEditorCloseScene`)
* Remove CExportManager::AddStatObj/AddMeshes/AddMesh,
IExportManager::ExportSingleStatObj
* Remove CIconManager/IIconManager::GetObject
* Remove CBaseObject::IntersectRayMesh
* Remove IIndexedMesh and related structs.
* Unused IUndoObject::GetDescription and all derived implementations.
* Unused CUndoBaseLibrary/CUndoBaseLibraryManager
* Unused Matrix34_tpl typedefs
* Legacy Xml classes cleanup

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* Remove unused `Vec3ToVector3D` helper

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

* fix - remove unused static variable

Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com>

Co-authored-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>
2021-11-29 11:21:06 -07:00

3348 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) : AZStd::string{});
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); }
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;
AZStd::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 static_cast<float>(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();
//////////////////////////////////////////////////////////////////////////
ISplineInterpolator* pSpline = splineInfo.pSpline;
ISplineInterpolator* pDetailSpline = splineInfo.pDetailSpline;
if (!pSpline)
{
return;
}
int nTotalNumberOfDimensions(0);
int nCurrentDimension(0);
int left = static_cast<int>(TimeToXOfs(startTime));//rcClip.left;
int right = static_cast<int>(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, static_cast<int>(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(static_cast<int>(x), m_rcSpline.top(), static_cast<int>(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 (size_t 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 (size_t 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, static_cast<int>(splineIndex), static_cast<int>(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 (size_t 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 (size_t 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 = static_cast<int>(TimeToXOfs(affectedRangeMin));
int rangeMax = static_cast<int>(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 (size_t 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 (size_t 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 (size_t 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 (size_t 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));
size_t keyTimeIndex = (itKeyTime != m_keyTimes.end() ? itKeyTime - m_keyTimes.begin() : m_keyTimes.size());
int redrawRangeStart = (keyTimeIndex >= 2 ? static_cast<int>(TimeToXOfs(m_keyTimes[keyTimeIndex - 2].time)) : m_rcSpline.left());
int redrawRangeEnd = (keyTimeIndex < m_keyTimes.size() - 2 ? static_cast<int>(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 (size_t 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 (size_t 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 (size_t 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 (size_t 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 (size_t 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 (size_t 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 (size_t 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 (size_t 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 (size_t 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>