Created
August 15, 2016 02:30
-
-
Save JSandusky/cfb7f83f04a4632f4b7c98442aadecbc to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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); | |
} | |
}; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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