mirror of
https://bitbucket.org/mfeemster/fractorium.git
synced 2025-01-21 05:00:06 -05:00
745f06d29d
-Remove the Type field from the variations tree and instead just put the type indicator icon next to the variation name. -Double clicking to toggle variation parameter spinners now resets the value to the default if there is one, else it uses zero. If it is already using the default, it is toggled to 0. -Add a new button to toggle xaos on and off. -When duplicating a flame, insert it immediately after the one being duplicated instead of at the end of the file. -When switching between flames in a file, keep the same xform index selected rather than resetting it to the first xform each time. -Create a threaded writer for the final render and EmberAnimate so the rendering process does not get delayed by file saving which may take a long time. -Remove warning which said "Frames per rot cannot be greater than one while Rotations is zero" when generating a sequence. -Add the Circle_Rand variation from Chaotica. -Add tool tips to clarify the following items: --Auto Unique Filenames checkbox in the options dialog. --Xaos table headers. --Bug fixes -Generating sequences using the following variations would be done incorrectly: circletrans1, collideoscope, crob, curlsp, glynnsim1, glynnsim2, hypercrop, julian, julian, mobiusn, nblur, waves2, wavesn. -Adding/removing nodes from the color curve had accidentally been disabled. -The applied xaos weight table was not showing normalized weight values. -Changing the size of a flame was not observing the Apply To All checkbox. -Do not clamp the Rotate field to +/-180, because this causes the rotation to switch from CW to CCW during sequence generation. Instead, leave it exactly as the user entered it so the rotations proceed in the same direction.
283 lines
8.2 KiB
C++
283 lines
8.2 KiB
C++
#include "FractoriumPch.h"
|
|
#include "CurvesGraphicsView.h"
|
|
|
|
/// <summary>
|
|
/// Construct the scene which will have a fixed rect.
|
|
/// Construct all points, pens and axes.
|
|
/// </summary>
|
|
/// <param name="parent">Pass to the parent</param>
|
|
CurvesGraphicsView::CurvesGraphicsView(QWidget* parent)
|
|
: QGraphicsView(parent)
|
|
{
|
|
m_Scene.setSceneRect(0, 0, 245, 245);
|
|
m_AxisPen = QPen(Qt::GlobalColor::white);
|
|
m_APen = QPen(Qt::GlobalColor::black); m_Pens[0] = &m_APen;
|
|
m_RPen = QPen(Qt::GlobalColor::red); m_Pens[1] = &m_RPen;
|
|
m_GPen = QPen(Qt::GlobalColor::green); m_Pens[2] = &m_GPen;
|
|
m_BPen = QPen(Qt::GlobalColor::blue); m_Pens[3] = &m_BPen;
|
|
m_APen.setWidth(2);
|
|
m_RPen.setWidth(2);
|
|
m_GPen.setWidth(2);
|
|
m_BPen.setWidth(2);
|
|
setScene(&m_Scene);
|
|
//qDebug() << "Original scene rect before setting anything is: " << sceneRect();
|
|
m_OriginalRect = sceneRect();
|
|
Curves<float> curves(true);
|
|
Set(curves);
|
|
show();
|
|
//qDebug() << "Original scene rect is: " << m_OriginalRect;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when an underlying point has had its position changed, so emit a signal so that a listener can take action.
|
|
/// </summary>
|
|
/// <param name="curveIndex">The curve whose point value was changed, 0-3.</param>
|
|
/// <param name="pointIndex">The point within the curve whose point value was changed.</param>
|
|
/// <param name="point">The position of the point. X,Y will each be within 0-1.</param>
|
|
void CurvesGraphicsView::PointChanged(int curveIndex, int pointIndex, const QPointF& point)
|
|
{
|
|
if (curveIndex == m_Index)
|
|
{
|
|
const auto x = point.x() / width();
|
|
const auto y = (height() - point.y()) / height();
|
|
emit PointChangedSignal(curveIndex, pointIndex, QPointF(x, y));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the position of a given point within a given curve.
|
|
/// </summary>
|
|
/// <param name="curveIndex">The curve whose point value will be retrieved, 0-3.</param>
|
|
/// <param name="pointIndex">The point within the curve whose value will be retrieved, 0-3.</param>
|
|
/// <returns>The position of the point. X,Y will each be within 0-1.</returns>
|
|
QPointF CurvesGraphicsView::Get(int curveIndex, int pointIndex)
|
|
{
|
|
if (curveIndex < 4 && pointIndex < m_Points[curveIndex].size())
|
|
{
|
|
if (EllipseItem* item = m_Points[curveIndex][pointIndex])
|
|
return QPointF(item->pos().x() / width(), (height() - item->pos().y()) / height());
|
|
}
|
|
|
|
return QPointF();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the position of a given point within a given curve.
|
|
/// </summary>
|
|
/// <param name="curveIndex">The curve whose point will be set, 0-3.</param>
|
|
/// <param name="pointIndex">The point within the curve which will be set, 0-3</param>
|
|
/// <param name="point">The position to set the point to. X,Y will each be within 0-1.</param>
|
|
void CurvesGraphicsView::Set(int curveIndex, int pointIndex, const QPointF& point)
|
|
{
|
|
if (curveIndex < 4 && pointIndex < m_Points[curveIndex].size())
|
|
{
|
|
m_Points[curveIndex][pointIndex]->setPos(point.x() * width(), (1.0 - point.y()) * height());//Scale to scene dimensions, Y axis is flipped.
|
|
}
|
|
}
|
|
|
|
void CurvesGraphicsView::Set(Curves<float>& curves)
|
|
{
|
|
m_Scene.clear();
|
|
m_XLine = new QGraphicsLineItem();
|
|
m_XLine->setPen(m_AxisPen);
|
|
m_XLine->setZValue(0);
|
|
m_YLine = new QGraphicsLineItem();
|
|
m_YLine->setPen(m_AxisPen);
|
|
m_YLine->setZValue(0);
|
|
m_Scene.addItem(m_XLine);
|
|
m_Scene.addItem(m_YLine);
|
|
auto createpoints = [&](int index, vector<EllipseItem*>& items, Qt::GlobalColor col, int zval)
|
|
{
|
|
items.clear();
|
|
m_Points[index].clear();
|
|
|
|
for (int i = 0; i < curves.m_Points[index].size(); i++)
|
|
{
|
|
auto item = new EllipseItem(QRectF(-5, -5, 10, 10), index, i, this);
|
|
items.push_back(item);
|
|
item->setBrush(QBrush(col));
|
|
m_Scene.addItem(item);
|
|
m_Points[index].push_back(item);
|
|
item->setZValue(zval);
|
|
const QPointF point(curves.m_Points[index][i].x, curves.m_Points[index][i].y);
|
|
Set(index, i, point);
|
|
}
|
|
};
|
|
createpoints(0, m_AllP, Qt::GlobalColor::black, 2);
|
|
createpoints(1, m_RedP, Qt::GlobalColor::red, 1);
|
|
createpoints(2, m_GrnP, Qt::GlobalColor::green, 1);
|
|
createpoints(3, m_BluP, Qt::GlobalColor::blue, 1);
|
|
SetTop(CurveIndex(m_Index));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the topmost curve but setting its Z value.
|
|
/// All other curves will get a value one less.
|
|
/// </summary>
|
|
/// <param name="curveIndex">The curve to set</param>
|
|
void CurvesGraphicsView::SetTop(CurveIndex curveIndex)
|
|
{
|
|
switch (curveIndex)
|
|
{
|
|
case CurveIndex::ALL:
|
|
m_Index = 0;
|
|
break;
|
|
|
|
case CurveIndex::RED:
|
|
m_Index = 1;
|
|
break;
|
|
|
|
case CurveIndex::GREEN:
|
|
m_Index = 2;
|
|
break;
|
|
|
|
case CurveIndex::BLUE:
|
|
default:
|
|
m_Index = 3;
|
|
}
|
|
|
|
for (size_t i = 0; i < 4; i++)
|
|
{
|
|
const auto b = (i == m_Index);
|
|
|
|
for (auto& p : m_Points[i])
|
|
p->SetCurrent(b);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Overridden paint even which draws the points, axes and curves.
|
|
/// </summary>
|
|
/// <param name="e">Ignored</param>
|
|
void CurvesGraphicsView::paintEvent(QPaintEvent* e)
|
|
{
|
|
QGraphicsView::paintEvent(e);
|
|
int i;
|
|
const QRectF rect = scene()->sceneRect();
|
|
const double w2 = width() / 2;
|
|
const double h2 = height() / 2;
|
|
//Draw axis lines.
|
|
m_XLine->setLine(QLineF(0, h2, width(), h2));
|
|
m_YLine->setLine(QLineF(w2, 0, w2, height()));
|
|
//This must be constructed every time and cannot be a member.
|
|
QPainter painter(viewport());
|
|
painter.setClipRect(rect);
|
|
painter.setRenderHint(QPainter::Antialiasing);
|
|
auto points = m_Points;
|
|
|
|
for (auto& p : points)
|
|
{
|
|
if (p.size() < 2)
|
|
return;
|
|
|
|
std::sort(p.begin(), p.end(), [&](auto & lhs, auto & rhs) { return lhs->pos().x() < rhs->pos().x(); });
|
|
}
|
|
|
|
//Create 4 new paths. These must be constructed every time and cannot be members.
|
|
//Need to sort the points here first based on their x coordinate.
|
|
QPainterPath paths[4] =
|
|
{
|
|
QPainterPath(points[0][0]->pos()),
|
|
QPainterPath(points[1][0]->pos()),
|
|
QPainterPath(points[2][0]->pos()),
|
|
QPainterPath(points[3][0]->pos())
|
|
};
|
|
int topmost = 0;
|
|
|
|
//Construct paths or all curves, and draw them for all but the topmost curve.
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
vector<v2F> vals;
|
|
vals.reserve(points[i].size());
|
|
|
|
for (auto& p : points[i])
|
|
vals.push_back({ p->pos().x(), p->pos().y() });
|
|
Spline<float> spline(vals);
|
|
|
|
for (int j = 0; j < rect.width(); j++)
|
|
{
|
|
const auto x = j;
|
|
const auto y = spline.Interpolate(x);
|
|
paths[i].lineTo(QPointF(x, y));
|
|
}
|
|
|
|
if (points[i][0]->zValue() == 1)
|
|
{
|
|
painter.setPen(*m_Pens[i]);
|
|
painter.drawPath(paths[i]);
|
|
}
|
|
else
|
|
topmost = i;
|
|
}
|
|
|
|
//Draw the topmost curve.
|
|
painter.setPen(*m_Pens[topmost]);
|
|
painter.drawPath(paths[topmost]);
|
|
}
|
|
|
|
void CurvesGraphicsView::mousePressEvent(QMouseEvent* e)
|
|
{
|
|
QGraphicsView::mousePressEvent(e);
|
|
|
|
if (e == nullptr)
|
|
return;
|
|
|
|
const auto thresh = devicePixelRatioF() * 4;
|
|
auto findpoint = [&](int x, int y, double thresh) -> int
|
|
{
|
|
for (int i = 0; i < m_Points[m_Index].size(); i++)
|
|
{
|
|
if (auto item = m_Points[m_Index][i])
|
|
{
|
|
const auto xdist = std::abs(item->pos().x() - x);
|
|
const auto ydist = std::abs(item->pos().y() - y);
|
|
const auto threshAgain = thresh;
|
|
|
|
if (xdist < threshAgain && ydist < threshAgain)
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
};
|
|
|
|
if (e->button() == Qt::RightButton)
|
|
{
|
|
const auto i = findpoint(e->pos().x(), e->pos().y(), thresh);
|
|
|
|
if (i != -1)
|
|
emit PointRemovedSignal(m_Index, i);
|
|
}
|
|
else if (findpoint(e->pos().x(), e->pos().y(), thresh * 2) == -1)
|
|
{
|
|
const auto rect = scene()->sceneRect();
|
|
auto points = m_Points[m_Index];
|
|
|
|
if (points.size() < 2)
|
|
return;
|
|
|
|
std::sort(points.begin(), points.end(), [&](auto & lhs, auto & rhs) { return lhs->pos().x() < rhs->pos().x(); });
|
|
vector<v2F> vals;
|
|
vals.reserve(points.size());
|
|
|
|
for (auto& p : points)
|
|
vals.push_back({ p->pos().x(), p->pos().y() });
|
|
Spline<float> spline(vals);
|
|
|
|
for (int j = 0; j < rect.width(); j++)
|
|
{
|
|
auto y = spline.Interpolate(j);
|
|
const auto xdist = std::abs(j - e->pos().x());
|
|
const auto ydist = std::abs(y - e->pos().y());
|
|
|
|
if (xdist < thresh && ydist < thresh)
|
|
{
|
|
const auto x = e->pos().x() / (double)width();
|
|
const auto y2 = (height() - e->pos().y()) / (double)height();
|
|
emit PointAddedSignal(m_Index, QPointF(x, y2));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|