Skip to content

Instantly share code, notes, and snippets.

@JSandusky
Created August 15, 2016 02:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JSandusky/cfb7f83f04a4632f4b7c98442aadecbc to your computer and use it in GitHub Desktop.
Save JSandusky/cfb7f83f04a4632f4b7c98442aadecbc to your computer and use it in GitHub Desktop.
float ColorCurve::GetY(float pos) const
{
for (int i = 0; i < knots_.size() - 1; ++i)
{
Vec2 cur = knots_[i];
Vec2 next = knots_[i + 1];
if (pos >= cur.x && pos <= next.x)
{
float t = (pos - cur.x) / (next.x - cur.x);
const float a = 1.0f - t;
const float b = t;
const float h = next.x - cur.x;
// Couldn't have figured this bit out without, the interpolation was my error:
// http://www.developpez.net/forums/d331608-3/general-developpement/algorithme-mathematiques/contribuez/image-interpolation-spline-cubique/#post3513925
return a * cur.y + b * next.y + (h * h / 6.0f) * ((a*a*a - a) * derivatives_[i] + (b*b*b - b) * derivatives_[i + 1]);
}
}
// Deal with edges
if (pos <= 0.0f)
return 0.0f;
if (pos >= 0.5f && knots_.size() > 0)
return knots_.back().y;
return 0.0f;
}
void ColorCurve::CalculateDerivatives()
{
const int count = knots_.size();
static const float XDiv = 1.0f / 6.0f; // Should be 2Pi?
static const float YDiv = 1.0f / 3.0f; // Should be Pi?
static const float ZDiv = 1.0f / 6.0f; // Shold be 2Pi?
derivatives_.clear();
std::vector<Vec3> knotTans(count);
derivatives_.resize(count, 0.0f);
knotTans[0] = Vec3(0, 1, 0);
knotTans[count - 1] = Vec3(0, 1, 0);
for (int i = 1; i < count - 1; ++i)
{
knotTans[i].x = (knots_[i].x - knots_[i - 1].x) * XDiv;
knotTans[i].y = (knots_[i + 1].x - knots_[i - 1].x) * YDiv;
knotTans[i].z = (knots_[i + 1].x - knots_[i].x) * ZDiv;
derivatives_[i] = (knots_[i + 1] - knots_[i]).YOverX() - (knots_[i] - knots_[i - 1]).YOverX();
}
for (int i = 1; i < count - 1; ++i)
{
const float m = knotTans[i].x / knotTans[i - 1].y;
knotTans[i][1] -= m * knotTans[i - 1].x;
knotTans[i][0] = 0;
derivatives_[i] -= m * derivatives_[i - 1];
}
for (int i = count - 2; i >= 0; --i)
{
const float m = knotTans[i].z / knotTans[i + 1].y;
knotTans[i][1] -= m * knotTans[i + 1].x;
knotTans[i][2] = 0;
derivatives_[i] -= m * derivatives_[i + 1];
}
for (int i = 0; i < count; ++i)
derivatives_[i] /= knotTans[i].y;
}
void ColorCurve::Deserialize(Deserializer* src)
{
unsigned ct = src->ReadUInt();
knots_.clear();
while (ct)
{
knots_.push_back(src->ReadVector2());
--ct;
}
if (knots_.size() > 0)
CalculateDerivatives();
}
void ColorCurve::Serialize(Serializer* dest) const
{
dest->WriteUInt((unsigned)knots_.size());
for (auto knot : knots_)
dest->WriteVector2(knot);
}
void ColorCurves::Deserialize(Deserializer* src)
{
R.Deserialize(src);
G.Deserialize(src);
B.Deserialize(src);
A.Deserialize(src);
}
void ColorCurves::Serialize(Serializer* dest) const
{
R.Serialize(dest);
G.Serialize(dest);
B.Serialize(dest);
A.Serialize(dest);
}
#include "ColorCurvesGraph.h"
#include <qpainter.h>
#include <qevent.h>
#include <qtooltip.h>
#include <sstream>
namespace SprueEditor
{
ColorCurvesGraph::ColorCurvesGraph(QWidget* parent) : QWidget(parent),
dragIndex_(-1)
{
painter_ = new QPainter();
setMouseTracking(true);
linePen_ = QPen(Qt::green, 2);
}
ColorCurvesGraph::~ColorCurvesGraph()
{
delete painter_;
}
bool ColorCurvesGraph::event(QEvent *evt)
{
if (evt->type() == QEvent::ToolTip)
{
QHelpEvent* data = static_cast<QHelpEvent*>(evt);
auto pt = data->pos();
const float w = width();
const float x = pt.x() / w;
float value = curve_.GetY(x);
std::stringstream ss;
ss.precision(2);
ss << "X: " << x << "\nY: " << value;
QToolTip::showText(data->globalPos(), ss.str().c_str());
}
return QWidget::event(evt);
}
void ColorCurvesGraph::paintEvent(QPaintEvent *event)
{
if (painting_)
return;
painting_ = true;
const float lower = (float)height();
const float upper = (float)0;
const float width = (float)this->width();
const float height = (float)this->height();
QColor color = this->palette().color(QPalette::Window);
QColor colorBG(30, 30, 30);
QBrush redBrush(QColor(255, 0, 0));
QBrush blueBrush(QColor(0, (unsigned char)(0.4787f * 255), (unsigned char)(0.8f * 255)));
QPen gridPen(QColor(90, 90, 90), 1);
painter_->begin(this);
// bg
painter_->fillRect(QRect(0, upper, width, height), colorBG);
// draw grey lines
{
painter_->setPen(gridPen);
for (int x = 1; x <= 3; ++x) // Vertical
painter_->drawLine(QPoint(width * 0.25f * x, lower), QPoint(width * 0.25f * x, upper));
for (int y = 1; y <= 3; ++y) // Horizontal
painter_->drawLine(QPoint(0.0f, height * 0.25f * y), QPoint(width, height * 0.25f * y));
// draw diagonal slope
painter_->drawLine(QPoint(0, height), QPoint(width, 0));
}
// Draw red lines for the 0.0 edges
painter_->setBrush(redBrush);
painter_->setPen(Qt::NoPen);
painter_->drawRect(QRect(0, 0, 1, this->height()));
painter_->drawRect(QRect(0, this->height() - 1, this->width(), this->height()));
// Draw cyan lines for 1.0 edges
painter_->setBrush(blueBrush);
painter_->setPen(Qt::NoPen);
painter_->drawRect(QRect(this->width() - 1, 0, 1, this->height()));
painter_->drawRect(QRect(0, 0, this->width(), 1));
for (auto bgCurve : backgroundCurves_)
paintCurve(painter_, bgCurve.second, bgCurve.first);
painter_->setPen(linePen_);
paintCurve(painter_, curve_, linePen_);
painter_->setPen(Qt::NoPen);
for (unsigned i = 0; i < curve_.knots_.size(); ++i)
{
if (i == dragIndex_)
painter_->setBrush(Qt::yellow);
else
painter_->setBrush(Qt::white);
painter_->drawRect((curve_.knots_[i].x) * this->width() - 5, this->height() - (curve_.knots_[i].y) * this->height() - 5, 10, 10);
}
painter_->end();
painting_ = false;
}
void ColorCurvesGraph::paintCurve(QPainter* painter, const SprueEngine::ColorCurve& curve, const QPen& pen) const
{
const float step = (float)(1.0f / (this->width() / 2));
// Draw the curve
float lastX = 0.0f;
float y = curve.GetY(0.0f);
float lastY = CLAMP01(1.0f - y) * height();
lastY = lastY != lastY ? 0.0f : lastY;
painter->setPen(pen);
for (float f = step; f < 1.0f; f += step)
{
float nextX = CLAMP01(f) * width();
float nextY = CLAMP01(1.0f - curve.GetY(f)) * height();
nextY = nextY != nextY ? 0.0f : nextY;
painter->drawLine(QPointF(lastX, CLAMP(lastY, 0, height() - 1)), QPointF(nextX, CLAMP(nextY, 0, height() - 1)));
lastX = nextX;
lastY = nextY;
}
float nextX = 1.0f * width();
float nextY = CLAMP01(1.0f - curve.GetY(1.0f)) * height();
nextY = nextY != nextY ? 0.0f : nextY;
painter->drawLine(QPointF(lastX, CLAMP(lastY, 0, height() - 1)), QPointF(nextX, CLAMP(nextY, 0, height() - 1)));
}
void ColorCurvesGraph::mousePressEvent(QMouseEvent* evt)
{
const float px = evt->pos().x() / (float)width();
const float py = evt->pos().y() / (float)height();
SprueEngine::Vec2 pt = ToVec2(evt->pos());
for (unsigned i = 0; i < curve_.knots_.size(); ++i)
{
if ((ToPt(curve_.knots_[i]) - evt->pos()).manhattanLength() < 10)
{
dragIndex_ = i;
update();
return;
}
}
curve_.knots_.push_back(pt);
std::sort(curve_.knots_.begin(), curve_.knots_.end(), [](const SprueEngine::Vec2& lhs, const SprueEngine::Vec2& rhs) {
return lhs.x < rhs.x;
});
curve_.CalculateDerivatives();
auto idx = std::find(curve_.knots_.begin(), curve_.knots_.end(), pt);
if (idx != curve_.knots_.end())
dragIndex_ = idx - curve_.knots_.begin();
evt->accept();
update();
emit CurveChanged();
}
void ColorCurvesGraph::mouseReleaseEvent(QMouseEvent* evt)
{
const float px = evt->pos().x() / (float)width();
const float py = evt->pos().y() / (float)height();
if (dragIndex_ != -1 && !(evt->modifiers() & Qt::KeyboardModifier::ControlModifier))
{
dragIndex_ = -1;
evt->accept();
emit CurveChanged();
return;
}
// Remove node
if (evt->modifiers() & Qt::KeyboardModifier::ControlModifier)
{
dragIndex_ = -1;
SprueEngine::Vec2 pt(px, py);
for (unsigned i = 0; i < curve_.knots_.size(); ++i)
{
if ((ToPt(curve_.knots_[i]) - evt->pos()).manhattanLength() < 10)
{
curve_.knots_.erase(curve_.knots_.begin() + i);
break;
}
}
if (curve_.knots_.size() < 2)
curve_.MakeLinear();
curve_.CalculateDerivatives();
evt->accept();
update();
emit CurveChanged();
}
}
void ColorCurvesGraph::mouseMoveEvent(QMouseEvent* evt)
{
if (dragIndex_ != -1)
{
SprueEngine::Vec2 pt = ToVec2(evt->pos());
if (dragIndex_ > 0)
pt.x = SprueMax(curve_.knots_[dragIndex_ - 1].x + 0.01f, pt.x);
if (dragIndex_ < curve_.knots_.size() - 1)
pt.x = SprueMin(curve_.knots_[dragIndex_ + 1].x - 0.01f, pt.x);
curve_.knots_[dragIndex_] = pt;
curve_.CalculateDerivatives();
evt->accept();
update();
}
}
}
#pragma once
#include <SprueEngine/ClassDef.h>
#include <SprueEngine/Math/MathDef.h>
#include <math.h>
namespace SprueEngine
{
enum CurveType
{
CT_Constant, // Fixed value
CT_Linear, // Algebra standard MX+B
CT_Quadratic, // Exponential
CT_Logistic, // Sigmoid
CT_Logit, // 90 degree Sigmoid (biology/psych origins)
CT_Threshold, // Boolean/stair
CT_Sine, // Sine wave
CT_Parabolic, // Algebra standard form parabola
CT_NormalDistribution, // Probability density function
CT_Bounce, // Bouncing degrading pattern, effectively decaying noise
};
struct SPRUE ResponseCurve
{
CurveType type_;
float xIntercept_;
float yIntercept_;
float slopeIntercept_;
float exponent_;
bool flipX_;
bool flipY_;
ResponseCurve() : type_(CT_Linear), xIntercept_(0.0f), yIntercept_(0.0f), slopeIntercept_(1.0f), exponent_(1.0f), flipX_(false), flipY_(false)
{
xIntercept_ = yIntercept_ = 0.0f;
}
float GetValue(float x) const
{
if (flipX_)
x = 1.0f - x;
// Evaluate the curve function for the given inputs.
float value = 0.0f;
switch (type_)
{
case CT_Constant:
value = yIntercept_;
break;
case CT_Linear:
// y = m(x - c) + b ... x expanded from standard mx+b
value = (slopeIntercept_ * (x - xIntercept_)) + yIntercept_;
break;
case CT_Quadratic:
// y = mx * (x - c)^K + b
value = ((slopeIntercept_ * x) * powf(abs(x + xIntercept_), exponent_)) + yIntercept_;
break;
case CT_Logistic:
// y = (k * (1 / (1 + (1000m^-1*x + c))) + b
value = (exponent_ * (1.0f / (1.0f + powf(abs(1000.0f * slopeIntercept_), (-1.0f * x) + xIntercept_ + 0.5f)))) + yIntercept_; // Note, addition of 0.5 to keep default 0 XIntercept sane
break;
case CT_Logit:
// y = -log(1 / (x + c)^K - 1) * m + b
value = (-logf((1.0f / powf(abs(x - xIntercept_), exponent_)) - 1.0f) * 0.05f * slopeIntercept_) + (0.5f + yIntercept_); // Note, addition of 0.5f to keep default 0 XIntercept sane
break;
case CT_Threshold:
value = x > xIntercept_ ? (1.0f - yIntercept_) : (0.0f - (1.0f - slopeIntercept_));
break;
case CT_Sine:
// y = sin(m * (x + c)^K + b
value = (sinf(slopeIntercept_ * powf(x + xIntercept_, exponent_)) * 0.5f) + 0.5f + yIntercept_;
break;
case CT_Parabolic:
// y = mx^2 + K * (x + c) + b
value = powf(slopeIntercept_ * (x + xIntercept_), 2) + (exponent_ * (x + xIntercept_)) + yIntercept_;
break;
case CT_NormalDistribution:
// y = K / sqrt(2 * PI) * 2^-(1/m * (x - c)^2) + b
value = (exponent_ / (sqrtf(2 * 3.141596f))) * powf(2.0f, (-(1.0f / (abs(slopeIntercept_) * 0.01f)) * powf(x - (xIntercept_ + 0.5f), 2.0f))) + yIntercept_;
break;
case CT_Bounce:
value = abs(sinf((6.28f * exponent_) * (x + xIntercept_ + 1.0f) * (x + xIntercept_ + 1.0f)) * (1.0f - x) * slopeIntercept_) + yIntercept_;
break;
}
// Invert the value if specified as an inverse.
if (flipY_)
value = 1.0f - value;
// Constrain the return to a normal 0-1 range.
return CLAMP01(value);
}
};
}
#include "ResponseCurveGraph.h"
#include <qpainter.h>
#include <qevent.h>
#include <qtooltip.h>
#include <sstream>
namespace SprueEditor
{
float safetyCheck(float input) {
return input != input ? 0.0f : input;
}
ResponseCurveGraph::ResponseCurveGraph(QWidget* parent) : QWidget(parent)
{
painter_ = new QPainter();
setMouseTracking(true);
}
ResponseCurveGraph::~ResponseCurveGraph()
{
delete painter_;
}
bool ResponseCurveGraph::event(QEvent *evt)
{
if (evt->type() == QEvent::ToolTip)
{
QHelpEvent* data = static_cast<QHelpEvent*>(evt);
auto pt = data->pos();
const float w = width();
const float x = pt.x() / w;
float value = curve_.GetValue(x);
std::stringstream ss;
ss.precision(2);
ss << "X: " << x << "\nY: " << value;
QToolTip::showText(data->globalPos(), ss.str().c_str());
}
return QWidget::event(evt);
}
void ResponseCurveGraph::paintEvent(QPaintEvent *event)
{
const float lower = (float)height();
const float upper = (float)0;
const float width = (float)this->width();
const float height = (float)this->height();
QColor color = this->palette().color(QPalette::Window);
QColor colorBG(30, 30, 30);
QBrush redBrush(QColor(255, 0, 0));
QBrush blueBrush(QColor(0, (unsigned char)(0.4787f * 255), (unsigned char)(0.8f * 255)));
QPen gridPen(QColor(90,90,90), 1);
painter_->begin(this);
// bg
painter_->fillRect(QRect(0, upper, width, height), colorBG);
// draw grey lines
{
painter_->setPen(gridPen);
for (int x = 1; x <= 3; ++x) // Vertical
painter_->drawLine(QPoint(width * 0.25f * x, lower), QPoint(width * 0.25f * x, upper));
for (int y = 1; y <= 3; ++y) // Horizontal
painter_->drawLine(QPoint(0.0f, height * 0.25f * y), QPoint(width, height * 0.25f * y));
// draw diagonal slope
painter_->drawLine(QPoint(0, height), QPoint(width, 0));
}
// Draw red lines for the 0.0 edges
painter_->setBrush(redBrush);
painter_->setPen(Qt::NoPen);
painter_->drawRect(QRect(0, 0, 1, this->height()));
painter_->drawRect(QRect(0, this->height() - 1, this->width(), this->height()));
// Draw cyan lines for 1.0 edges
painter_->setBrush(blueBrush);
painter_->setPen(Qt::NoPen);
painter_->drawRect(QRect(this->width() - 1, 0, 1, this->height()));
painter_->drawRect(QRect(0, 0, this->width(), 1));
QPen greenPen(QColor(0, 255, 0), 2);
paintCurve(painter_, curve_, greenPen);
painter_->end();
}
void ResponseCurveGraph::paintCurve(QPainter* painter, const SprueEngine::ResponseCurve& curve, const QPen& pen) const
{
const float step = (float)(1.0f / (this->width()/2));
// Draw the curve
float lastX = 0.0f;
float lastY = safetyCheck(CLAMP01(1.0f - curve.GetValue(0.0f)) * height());
painter->setPen(pen);
for (float f = step; f < 1.0f; f += step)
{
float nextX = CLAMP01(f) * width();
float nextY = safetyCheck(CLAMP01(1.0f - curve.GetValue(f))) * height();
painter->drawLine(QPointF(lastX, CLAMP(lastY, 0, height() - 1)), QPointF(nextX, CLAMP(nextY, 0, height() - 1)));
lastX = nextX;
lastY = nextY;
}
float nextX = 1.0f * width();
float nextY = safetyCheck(CLAMP01(1.0f - curve.GetValue(1.0f))) * height();
painter->drawLine(QPointF(lastX, CLAMP(lastY, 0, height() - 1)), QPointF(nextX, CLAMP(nextY, 0, height() - 1)));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment