#pragma once
#include "Utils.h"
#include "Isaac.h"
///
/// Curves class.
///
namespace EmberNs
{
///
/// The Bezier curves used to adjust the colors during final accumulation.
/// This functionality was gotten directly from Apophysis.
///
template
class EMBER_API Curves
{
public:
///
/// Constructor which sets the curve and weight values to their defaults.
///
Curves(bool init = false)
{
if (init)
Init();
else
Clear();
}
///
/// Default copy constructor.
///
/// The Curves object to copy
Curves(const Curves& curves)
{
Curves::operator=(curves);
}
///
/// Copy constructor to copy a Curves object of type U.
/// Special case that must be here in the header because it has
/// a second template parameter.
///
/// The Curves object to copy
template
Curves(const Curves& curves)
{
Curves::operator=(curves);
}
///
/// Default assignment operator.
///
/// The Curves object to copy
Curves& operator = (const Curves& curves)
{
if (this != &curves)
Curves::operator=(curves);
return *this;
}
///
/// Assignment operator to assign a Curves object of type U.
///
/// The Curves object to copy
/// Reference to updated self
template
Curves& operator = (const Curves& curves)
{
for (size_t i = 0; i < 4; i++)
{
m_Points[i][0].x = T(curves.m_Points[i][0].x); m_Points[i][0].y = T(curves.m_Points[i][0].y); m_Weights[i].x = T(curves.m_Weights[i].x);
m_Points[i][1].x = T(curves.m_Points[i][1].x); m_Points[i][1].y = T(curves.m_Points[i][1].y); m_Weights[i].y = T(curves.m_Weights[i].y);
m_Points[i][2].x = T(curves.m_Points[i][2].x); m_Points[i][2].y = T(curves.m_Points[i][2].y); m_Weights[i].z = T(curves.m_Weights[i].z);
m_Points[i][3].x = T(curves.m_Points[i][3].x); m_Points[i][3].y = T(curves.m_Points[i][3].y); m_Weights[i].w = T(curves.m_Weights[i].w);
}
return *this;
}
///
/// Unary addition operator to add a Curves object to this one.
///
/// The Curves object to add
/// Reference to updated self
Curves& operator += (const Curves& curves)
{
for (size_t i = 0; i < 4; i++)
{
m_Points[i][0] += curves.m_Points[i][0];
m_Points[i][1] += curves.m_Points[i][1];
m_Points[i][2] += curves.m_Points[i][2];
m_Points[i][3] += curves.m_Points[i][3];
m_Weights[i] += curves.m_Weights[i];
}
return *this;
}
///
/// Unary multiplication operator to multiply this object by another Curves object.
///
/// The Curves object to multiply this one by
/// Reference to updated self
Curves& operator *= (const Curves& curves)
{
for (size_t i = 0; i < 4; i++)
{
m_Points[i][0] *= curves.m_Points[i][0];
m_Points[i][1] *= curves.m_Points[i][1];
m_Points[i][2] *= curves.m_Points[i][2];
m_Points[i][3] *= curves.m_Points[i][3];
m_Weights[i] *= curves.m_Weights[i];
}
return *this;
}
///
/// Unary multiplication operator to multiply this object by a scalar of type T.
///
/// The scalar to multiply this object by
/// Reference to updated self
Curves& operator *= (const T& t)
{
for (size_t i = 0; i < 4; i++)
{
m_Points[i][0] *= t;
m_Points[i][1] *= t;
m_Points[i][2] *= t;
m_Points[i][3] *= t;
m_Weights[i] *= t;
}
return *this;
}
///
/// Set the curve and weight values to their default state.
///
void Init()
{
for (size_t i = 0; i < 4; i++)
{
m_Points[i][0] = v2T(0);//0,0 -> 0,0 -> 1,1 -> 1,1.
m_Points[i][1] = v2T(0);
m_Points[i][2] = v2T(1);
m_Points[i][3] = v2T(1);
m_Weights[i] = v4T(1);
}
}
///
/// Set the curve and weight values to an empty state.
///
void Clear()
{
memset(&m_Points, 0, sizeof(m_Points));
memset(&m_Weights, 0, sizeof(m_Weights));
}
///
/// Whether any points are not the default.
///
/// True if any point has been set to a value other than the default, else false.
bool CurvesSet()
{
bool set = false;
for (size_t i = 0; i < 4; i++)
{
if ((m_Points[i][0] != v2T(0)) ||
(m_Points[i][1] != v2T(0)) ||
(m_Points[i][2] != v2T(1)) ||
(m_Points[i][3] != v2T(1)))
{
set = true;
break;
}
}
return set;
}
///
/// Wrapper around calling BezierSolve() on each of the 4 weight and point vectors.
///
/// The position to apply
/// vec4 that contains the y component of the solution for each vector in each component
v4T BezierFunc(const T& t)
{
v4T result;
v2T solution(0, 0);
BezierSolve(t, m_Points[0], &m_Weights[0], solution); result.x = solution.y;
BezierSolve(t, m_Points[1], &m_Weights[1], solution); result.y = solution.y;
BezierSolve(t, m_Points[2], &m_Weights[2], solution); result.z = solution.y;
BezierSolve(t, m_Points[3], &m_Weights[3], solution); result.w = solution.y;
return result;
}
private:
///
/// Solve the given point and weight vectors for the given position and store
/// the output in the solution vec2 passed in.
///
/// The position to apply
/// A pointer to an array of 4 vec2
/// A pointer to an array of 4 weights
/// The vec2 to store the solution in
void BezierSolve(const T& t, v2T* src, v4T* w, v2T& solution)
{
T s, s2, s3, t2, t3, nom_x, nom_y, denom;
s = 1 - t;
s2 = s * s;
s3 = s * s * s;
t2 = t * t;
t3 = t * t * t;
nom_x = (w->x * s3 * src->x) + (w->y * s2 * 3 * t * src[1].x) + (w->z * s * 3 * t2 * src[2].x) + (w->w * t3 * src[3].x);
nom_y = (w->x * s3 * src->y) + (w->y * s2 * 3 * t * src[1].y) + (w->z * s * 3 * t2 * src[2].y) + (w->w * t3 * src[3].y);
denom = (w->x * s3) + (w->y * s2 * 3 * t) + (w->z * s * 3 * t2) + (w->w * t3);
if (std::isnan(nom_x) || std::isnan(nom_y) || std::isnan(denom) || denom == 0)
return;
solution.x = nom_x / denom;
solution.y = nom_y / denom;
}
public:
v2T m_Points[4][4];
v4T m_Weights[4];
};
//Must declare this outside of the class to provide for both orders of parameters.
///
/// Multiplication operator to multiply a Curves object by a scalar of type T.
///
/// The curves object to multiply
/// The scalar to multiply curves by by
/// Copy of new Curves
template
Curves operator * (const Curves& curves, const T& t)
{
Curves c(curves);
for (size_t i = 0; i < 4; i++)
{
c.m_Points[i][0] *= t;
c.m_Points[i][1] *= t;
c.m_Points[i][2] *= t;
c.m_Points[i][3] *= t;
c.m_Weights[i] *= t;
}
return c;
}
///
/// Multiplication operator for reverse order.
///
/// The scalar to multiply curves by by
/// The curves object to multiply
/// Copy of new Curves
template
Curves operator * (const T& t, const Curves& curves)
{
return curves * t;
}
}