// C RunTime Header Files
#include <cstdlib>
#include <cmath>
#include <cstdio>
#include <ctime>
#include <climits>
// C++ Headers
#include <iostream>
#include <iomanip>
#include <fstream>
#include <vector>
#include <map>
// Project Headers
#include "Graphics.h"
#include "Common.h"
#include "MemoryMapping.h"
#include "Window.h"
#include "TrueType.h"
BEGIN_NAMESPACE
void LogMessage(LogLevel a_level, const char* a_utf8TextFmt, ...);
// Using Debug can be used for quickly adding debug output, and in release builds the calls to it shouldn't
// produce any code (if NDEBUG is defined which the ANSI C standard defines for disabling assert in release builds)
#ifdef NDEBUG
# define Debug(a_utf8TextFmt, ...) ((void)0)
#else
# define Debug(a_utf8TextFmt, ...) LogMessage(DEBUG, a_utf8TextFmt, __VA_ARGS__)
#endif
static uint32_t blendColors(uint32_t a_color1, uint32_t a_color2, uint8_t a_alpha)
{
unsigned alpha2 = 255 - a_alpha;
unsigned red = ((a_color1 >> 16) & 0xff) * a_alpha + ((a_color2 >> 16) & 0xff) * alpha2;
unsigned green = ((a_color1 >> 8) & 0xff) * a_alpha + ((a_color2 >> 8) & 0xff) * alpha2;
unsigned blue = ((a_color1 >> 0) & 0xff) * a_alpha + ((a_color2 >> 0) & 0xff) * alpha2;
return 0xff000000 | ((red & 0xff00) << 8) | (green & 0xff00) | (blue >> 8);
}
static uint32_t blendColorsF(uint32_t a_color1, uint32_t a_color2, float a_alpha)
{
float alpha2 = 1.0f - a_alpha;
float red = float((a_color1 >> 16) & 0xff) * a_alpha + float((a_color2 >> 16) & 0xff) * alpha2;
float green = float((a_color1 >> 8) & 0xff) * a_alpha + float((a_color2 >> 8) & 0xff) * alpha2;
float blue = float((a_color1 >> 0) & 0xff) * a_alpha + float((a_color2 >> 0) & 0xff) * alpha2;
return 0xff000000 | ((int(red) & 0xff) << 16) | ((int(green) & 0xff) << 8) | (int(blue) & 0xff);
}
/*
template <typename functor>
void ForEachPixel(PixelBuffer* a_target, uint32_t a_color, int a_x, int a_y, int a_width, int a_height)
{
for (int j = 0; j < a_height; j++)
for (int i = 0; i < a_width; i++)
{
int x = i + a_x;
int y = j + a_y;
if ( x >= 0 && x < a_target->m_width && y >= 0 && y < a_target->m_height ) {
uint32_t *dst = &(a_target->m_pixels[y*a_target->m_strideBytes/4 + x]);
functor.func();
}
}
}
*/
void DrawRectangleAlpha(PixelBuffer* a_target, uint32_t a_color, int a_x, int a_y, int a_width, int a_height)
{
for (int j = 0; j < a_height; j++)
for (int i = 0; i < a_width; i++)
{
int x = i + a_x;
int y = j + a_y;
if ( x >= 0 && x < a_target->m_width && y >= 0 && y < a_target->m_height ) {
uint32_t *dst = &(a_target->m_pixels[y*a_target->m_strideBytes/4 + x]);
*dst = blendColors(*dst, a_color, a_color >> 24);
}
}
}
void DrawRectangle(PixelBuffer* a_target, uint32_t a_color, int a_x, int a_y, int a_width, int a_height)
{
for (int j = 0; j < a_height; j++)
for (int i = 0; i < a_width; i++)
{
int x = i + a_x;
int y = j + a_y;
if ( x >= 0 && x < a_target->m_width && y >= 0 && y < a_target->m_height ) {
uint32_t *dst = &(a_target->m_pixels[y*a_target->m_strideBytes/4 + x]);
*dst = a_color;
}
}
}
void DrawEllipse(PixelBuffer* a_target, uint32_t a_color, int a_x, int a_y, int a_width, int a_height, bool a_smoothEdge)
{
int alpha = (a_color >> 24) & 0xff;
if ( alpha == 0 )
return;
int centerX = a_width/2;
int centerY = a_height/2;
int scale = 64; // This is to stop overflows with 32bit ints
if ( a_width < 256 && a_height < 256 )
scale = 1;
if ( alpha == 255 )
{
for (int j = 0; j < a_height; j++)
for (int i = 0; i < a_width; i++)
{
int x = i + a_x;
int y = j + a_y;
if ( x >= 0 && x < a_target->m_width && y >= 0 && y < a_target->m_height ) {
uint32_t *dst = &(a_target->m_pixels[y*a_target->m_strideBytes/4 + x]);
int a = (i - centerX) * a_height / scale;
int b = (j - centerY) * a_width / scale;
int c1 = a_width * a_height / (2 * scale);
int c2 = (a_width-1) * (a_height-1) / (2 * scale);
//int c2 = ((a_width+1) * (a_height+1)) / (2 * scale);
int innerR = c1*c1;
int outerR = c2*c2;
int dist = a*a + b*b;
int diffR = outerR - innerR;
int distR = dist - innerR;
int alpha = ((distR * 255) / diffR);
if ( dist < outerR )
*dst = a_color;
else if ( dist < innerR )
*dst = blendColors(a_color, *dst, alpha);
//a_antiAlias
}
}
} else {
for (int j = 0; j < a_height; j++)
for (int i = 0; i < a_width; i++)
{
int x = i + a_x;
int y = j + a_y;
if ( x >= 0 && x < a_target->m_width && y >= 0 && y < a_target->m_height ) {
uint32_t *dst = &(a_target->m_pixels[y*a_target->m_strideBytes/4 + x]);
int a = (i - centerX) * a_height / scale;
int b = (j - centerY) * a_width / scale;
int c = a_width * a_height / (2 * scale);
if ( (a*a + b*b) < c*c )
*dst = (alpha<<24) | (blendColors(a_color, *dst, alpha) & 0xffffff);
}
}
}
}
void DrawGradient(PixelBuffer* a_target, Gradient a_gradient, int a_x, int a_y, int a_width, int a_height)
{
for ( int j = 0; j < a_height; j++ )
for ( int i = 0; i < a_width; i++ )
{
int x = i + a_x;
int y = j + a_y;
if ( x >= 0 && x < a_target->m_width && y >= 0 && y < a_target->m_height )
{
float dist = 0.0;
if (a_gradient.m_type == RADIAL_GRADIENT)
{
int centerX = a_gradient.m_data.m_radial.m_centerX;
int centerY = a_gradient.m_data.m_radial.m_centerY;
//float errLUT[16] = { 1.1, -1.1, 2.2, -2.2, 3.3, -3.3, 4.4, -4.4, 5.5, -5.5, 6.6, -6.6, 7.7, -7.7, 8.8, -8.8 };
//int dist = (int)(errLUT[rand() % 4] + sqrt(float((centerX - i)*(centerX - i) + (centerY - j)*(centerY - j))) * 256 / a_gradient.m_data.m_radial.m_distance);
dist = sqrt(float((centerX - i)*(centerX - i) + (centerY - j)*(centerY - j))) / a_gradient.m_data.m_radial.m_distance;
/*
float distf = sqrt(float((centerX - i)*(centerX - i) + (centerY - j)*(centerY - j))) * 256 / a_gradient.m_data.m_radial.m_distance;
float frac = distf - floor(distf) - 0.51;
float frac2 = frac + 0.7;
float frac3 = float(j*8) / a_height + float(i-90) / a_width;
if ( frac < 0.1 && frac > -0.1 )
frac = ( frac < 0.0 ) ? -0.081 : 0.08;
int dist = (int)(float(frac * 64) - float(frac2 * frac3) + distf);
//dist += ;
*/
//dist = (dist > 255) ? 255 : ((dist < 0) ? 0 : dist);
}
else if (a_gradient.m_type == LINEAR_GRADIENT)
{
/*
m = dy / dx
y = mx + c;
c = y - mx;
m = (y - c) / x;
*/
// TODO:
int y1 = a_gradient.m_data.m_linear.m_y1;
int y2 = a_gradient.m_data.m_linear.m_y2;
if ( y <= y1 )
dist = 0;
else if ( y >= y2 )
dist = 1.0;
else {
dist = float(y2 - y) / (y2 - y1);
}
}
uint32_t *dst = &(a_target->m_pixels[y*a_target->m_strideBytes/4 + x]);
dist = (dist > 1.0f) ? 1.0f : ((dist < 0.0f) ? 0.0f : dist);
*dst = blendColorsF(a_gradient.m_color1, a_gradient.m_color2, dist);
}
}
}
void DrawLine(PixelBuffer* a_target, uint32_t a_color, int a_x1, int a_y1, int a_x2, int a_y2)
{
float x = 0.0f;
float y = 0.0f;
float fdy = 1.0f;
float fdx = 1.0f;
int dx = a_x2 - a_x1;
int dy = a_y2 - a_y1;
int count = 1;
if ( !dy && !dx ) {
if ( a_x1 >= 0 && a_x1 < a_target->m_width && a_y1 >= 0 && a_y1 < a_target->m_height )
a_target->m_pixels[a_y1*a_target->m_strideBytes/4 + a_x1] = a_color;
return;
}
if ( dx*dx > dy*dy ) {
count = dx;
fdy = float(dy) / dx;
} else {
count = dy;
fdx = float(dx) / dy;
}
if ( count < 0 ) {
count = -count;
fdy = -fdy;
fdx = -fdx;
}
//count++;
for (; count--; x+=fdx, y+=fdy)
{
int xi = int(a_x1 + x);
int yi = int(a_y1 + y);
if ( xi >= 0 && xi < a_target->m_width && yi >= 0 && yi < a_target->m_height )
a_target->m_pixels[yi*a_target->m_strideBytes/4 + xi] = a_color;
}
}
void DrawPixels(PixelBuffer* a_target, uint32_t* a_bits, int a_x, int a_y, int a_width, int a_height, int a_xOffset, int a_yOffset, int a_pixelsWidth, int a_pixelsHeight)
{
for (int j = 0; j < a_height; j++)
for (int i = 0; i < a_width; i++)
{
int x = i + a_x;
int y = j + a_y;
if ( y > 0 && y < a_target->m_height && x > 0 && x < a_target->m_width )
{
uint32_t *dst = &(a_target->m_pixels[(j+a_y)*a_target->m_strideBytes/4 + (i+a_x)]);
uint32_t src = a_bits[(j+a_yOffset)*a_pixelsWidth+(i+a_xOffset)];
if ( src & 0xff000000 )
*dst = src;
}
}
}
void DrawPixelsAlpha(PixelBuffer* a_target, uint32_t* a_bits, int a_x, int a_y, int a_width, int a_height, int a_xOffset, int a_yOffset, int a_pixelsWidth, int a_pixelsHeight)
{
for (int j = 0; j < a_height; j++)
for (int i = 0; i < a_width; i++)
{
int x = i + a_x;
int y = j + a_y;
if ( y > 0 && y < a_target->m_height && x > 0 && x < a_target->m_width )
{
uint32_t *dst = &(a_target->m_pixels[(j+a_y)*a_target->m_strideBytes/4 + (i+a_x)]);
uint32_t src = a_bits[(j+a_yOffset)*a_pixelsWidth+(i+a_xOffset)];
if ( src & 0xff000000 )
*dst = blendColors(src, *dst, (src>>24) & 0xff);
}
}
}
void DrawPixelsAlphaBlended(PixelBuffer* a_target, uint32_t* a_bits, int a_x, int a_y, int a_width, int a_height, int a_xOffset, int a_yOffset, int a_pixelsWidth, int a_pixelsHeight, int a_alpha)
{
for (int j = 0; j < a_height; j++)
for (int i = 0; i < a_width; i++)
{
int x = i + a_x;
int y = j + a_y;
if ( y > 0 && y < a_target->m_height && x > 0 && x < a_target->m_width )
{
uint32_t *dst = &(a_target->m_pixels[(j+a_y)*a_target->m_strideBytes/4 + (i+a_x)]);
uint32_t src = a_bits[(j+a_yOffset)*a_pixelsWidth+(i+a_xOffset)];
if ( src & 0xff000000 )
*dst = blendColors(src, *dst, a_alpha);
}
}
}
#if 0
static bool TestFileExtension(const char* a_file, const char* a_ext)
{
size_t len = strlen(a_file);
if (len < 4 || strlen(a_ext) != 4)
return false;
for (int i = 0; i < 4; i++)
if (toupper(a_file[len-4+i]) != toupper(a_ext[i]))
return false;
return true;
}
extern int DecodePNG(std::vector<unsigned char>& out_image, unsigned long& image_width, unsigned long& image_height, const unsigned char* in_png, size_t in_size, bool convert_to_rgba32);
static bool LoadImage(const char* file, PixelBuffer& a_img, bool chromaKeyed = true)
{
if (TestFileExtension(file, ".png"))
{
// It's a PNG image
MemoryMappingData* mapping = MemoryMapping_Open(file);
if (!mapping)
return false;
// For 32bit builds, this code won't support input PNG files that are more than 4GiB, but neither can it support
// having a buffer as large as that for storing the pixels in to, so perhaps best just to use 64bit builds if dealing with massive images.
size_t siz = (size_t)MemoryMapping_GetSize(mapping);
uint8_t* buf = (uint8_t*)MemoryMapping_GetAddress(mapping);
std::vector<unsigned char> out_image;
unsigned long w, h;
if (DecodePNG(out_image, w, h, buf, siz, true))
{
a_img.m_height = h;
a_img.m_width = w;
a_img.m_strideBytes = w * sizeof(uint32_t);
a_img.m_format = PF_ARGB8888;
a_img.m_pixels = new uint32_t[w * h];
memcpy(a_img.m_pixels, out_image.data(), w * h * sizeof(uint32_t));
for ( int i = 0; i < w*h; i++ )
a_img.m_pixels[i] = ((a_img.m_pixels[i] << 16) & 0xff0000) |
((a_img.m_pixels[i] >> 16) & 0xff) | (a_img.m_pixels[i] & 0xff00ff00);
if ( chromaKeyed )
for ( int i = 0; i < w*h; i++ )
if ( a_img.m_pixels[i] != 0x00ff00 )
a_img.m_pixels[i] |= 0xff000000;
return true;
}
}
return false;
}
void DrawPixmapFile(PixelBuffer* a_target, const char* file, int x, int y, int x1, int y1, int x2, int y2)
{
static std::map<const char*,PixelBuffer> imageMap; // pixmap cache
PixelBuffer img = imageMap[file];
if (!img.m_pixels)
{
if (LoadImage(file, img, false))
{
imageMap[file] = img;
}
}
DrawPixels(a_target, img.m_pixels, x, y, x2-x1+1, y2-y1+1, x1, y1, img.m_width, img.m_height);
}
void DrawNumber(PixelBuffer* a_target, uint32_t col, int x, int y, int num, int width)
{
// LoadImage("numbers.png", g_numbersPixelsW, g_numbersPixelsH, g_numbersPixels, false);
int divisor = 1;
for (int i = width; i != 0; i--, divisor*=10)
DrawPixels(a_target, g_numbersPixels, x + (i-1)*16, y, 16, 30, 0, 30*((num/divisor)%10), g_numbersPixelsW, g_numbersPixelsH);
}
#endif
void LogMessage(LogLevel a_level, const char* a_formatString, ...)
{
char buf[1024];
va_list vaargs;
va_start(vaargs, a_formatString);
vsnprintf(buf, 1024, a_formatString, vaargs);
buf[1023] = 0; // Ensure null terminated
#ifdef _WIN32
OutputDebugStringA(buf);
#endif
puts(buf);
va_end(vaargs);
}
void GetTextExtents(PixelBuffer* a_target, const char* a_fontFamily, int a_size, const char* a_utf8String, int& a_width, int& a_height)
{
// TODO: do this properly
a_width = (int)strlen(a_utf8String) * (a_size / 2);
a_height = a_size;
}
struct vec2i
{
int x, y;
};
// Simple 3 control point bezier curve
// p1 and p3 are the 2 end points the curve starts/ends at
// p2 is another control point but not a point on the curve
// t is the ratio between the two end points to interpolate a result for
vec2i bezierCurve(const vec2i& p1, const vec2i& p2, const vec2i& p3, float t)
{
float t2 = 1.0f - t;
return (vec2i){ int(t2*t2*p1.x + 2.0*t2*t*p2.x + t*t*p3.x),
int(t2*t2*p1.y + 2.0*t2*t*p2.y + t*t*p3.y) };
}
vec2i bezierCurveFixed(const vec2i& p1, const vec2i& p2, const vec2i& p3, int t) // t is 0 to 1023
{
int t2 = 1023 - t;
return (vec2i){ (t2*t2*p1.x + 2*t2*t*p2.x + t*t*p3.x) >> 20,
(t2*t2*p1.y + 2*t2*t*p2.y + t*t*p3.y) >> 20 };
}
void DrawCurve(PixelBuffer* a_target, uint32_t a_color, const vec2i& p1, const vec2i& p2, const vec2i& p3)
{
int dx = p1.x - p3.x;
int dy = p1.y - p3.y;
if (dx*dx > dy*dy) {
dx = (dx < 0) ? -dx : dx;
} else {
dx = (dy < 0) ? -dy : dy;
}
dx *= 2;
/*
float divisor = 1.0f / float(dx);
for (int i = 0; i < (dx-1); i++)
{
vec2i pos1 = bezierCurve(p1, p2, p3, float(i) * divisor);
vec2i pos2 = bezierCurve(p1, p2, p3, float(i + 1) * divisor);
DrawLine(a_target, a_color, pos1.x, pos1.y, pos2.x, pos2.y);
}
*/
// Fixed precision version
for (int i = 0; i < dx; i++)
{
vec2i pos1 = bezierCurveFixed(p1, p2, p3, (i * 1024) / dx);
vec2i pos2 = bezierCurveFixed(p1, p2, p3, (i * 1024 + 1024) / dx);
DrawLine(a_target, a_color, pos1.x, pos1.y, pos2.x, pos2.y);
}
}
Rectangle glyphBounds(const GlyphOutline& outline)
{
Rectangle ret;
int minX = INT_MAX, minY = INT_MAX;
int maxX = INT_MIN, maxY = INT_MIN;
for (size_t i = 0; i < outline.m_lines.size(); i++)
{
const FontLineSegment& b = outline.m_lines[i];
if (b.m_x1 < minX) minX = b.m_x1; if (b.m_x1 > maxX) maxX = b.m_x1;
if (b.m_y1 < minY) minY = b.m_y1; if (b.m_y1 > maxY) maxY = b.m_y1;
if (b.m_x2 < minX) minX = b.m_x2; if (b.m_x2 > maxX) maxX = b.m_x2;
if (b.m_y2 < minY) minY = b.m_y2; if (b.m_y2 > maxY) maxY = b.m_y2;
if (b.m_x3 < minX) minX = b.m_x3; if (b.m_x3 > maxX) maxX = b.m_x3;
if (b.m_y3 < minY) minY = b.m_y3; if (b.m_y3 > maxY) maxY = b.m_y3;
}
ret.m_x = minX; ret.m_y = minY;
ret.m_width = maxX - minX + 1; ret.m_height = maxY - minY + 1;
return ret;
}
void DrawOutlineText(PixelBuffer* a_target, uint32_t a_color, const char* a_fontFamily, int a_size, int a_x, int a_y, const char* a_utf8String)
{
if (!a_target || !a_fontFamily || !a_utf8String || a_size <= 0)
{
return;
}
TtfFont& font = getFont("Arial");
while (*a_utf8String)
{
Glyph& glyph = font.getGlyph(*a_utf8String);
GlyphOutline& outline = glyph.outline;
GlyphMetrics& g = glyph.metrics;
// iterate the bezier curves
for (size_t i = 0; i < outline.m_lines.size(); i++)
{
FontLineSegment& b = outline.m_lines[i];
{
vec2i p1 = (vec2i){ a_x + int(b.m_x1 * a_size >> 10), a_y + a_size - int(b.m_y1 * a_size >> 10) };
vec2i p2 = (vec2i){ a_x + int(b.m_x2 * a_size >> 10), a_y + a_size - int(b.m_y2 * a_size >> 10) };
vec2i p3 = (vec2i){ a_x + int(b.m_x3 * a_size >> 10), a_y + a_size - int(b.m_y3 * a_size >> 10) };
DrawCurve(a_target, a_color, p1, p2, p3);
}
}
float spacing = 1.0;
a_x += ((g.advanceWidth * a_size) >> 10) + (int)(spacing + 0.5);
a_utf8String++;
}
}
void DrawText(PixelBuffer* a_target, uint32_t a_color, const char* a_fontFamily, int a_size, int a_x, int a_y, const char* a_utf8String)
{
if (!a_target || !a_fontFamily || !a_utf8String || a_size <= 0)
{
return;
}
// TODO: Loop over the string and break on line-breaks and call some internal function which does the body of this function
TtfFont& font = getFont("Arial");
while (*a_utf8String)
{
Glyph& glyph = font.getGlyph(*a_utf8String);
GlyphOutline& outline = glyph.outline;
GlyphMetrics& g = glyph.metrics;
Rectangle rect = glyphBounds(outline);
// iterate the bezier curves
for (size_t i = 0; i < outline.m_lines.size(); i++)
{
FontLineSegment& b = outline.m_lines[i];
{
vec2i p1 = (vec2i){ a_x + int(b.m_x1 * a_size >> 10), a_y + a_size - int(b.m_y1 * a_size >> 10) };
vec2i p2 = (vec2i){ a_x + int(b.m_x2 * a_size >> 10), a_y + a_size - int(b.m_y2 * a_size >> 10) };
vec2i p3 = (vec2i){ a_x + int(b.m_x3 * a_size >> 10), a_y + a_size - int(b.m_y3 * a_size >> 10) };
DrawCurve(a_target, a_color, p1, p2, p3);
}
}
float spacing = 1.0;
//a_x += -g.xMin + g.leftSideBearing + g.advance + (int)(spacing + 0.5);
a_x += ((g.advanceWidth * a_size) >> 10) + (int)(spacing + 0.5);
a_utf8String++;
}
// printf("done %s:%i: Here!\n", __FILE__, __LINE__);
}
/*
struct vec2f
{
float x, y;
};
vec2f operator*(const vec2f& v, float c)
{
return (vec2f){ v.x*c, v.y*c };
}
vec2f operator*(float c, const vec2f& v)
{
return (vec2f){ v.x*c, v.y*c };
}
vec2f operator+(const vec2f& v1, const vec2f& v2)
{
return (vec2f){ v1.x+v2.x, v1.y+v2.y };
}
// Generalized spline function
// a_tension -1.0 -> 1.0 Round -> Tight
// a_bias -1.0 -> 1.0 Pre shoot -> Post shoot
// a_continuity -1.0 -> 1.0 Box corners -> Inverted corners
vec2f kochanekBartelsSpline(const std::vector<vec2f>& a_points, float t, float a_tension, float a_bias, float a_continuity)
{
int pnts = a_points.size();
if (!pnts)
return (vec2f){ 0.0f, 0.0f };
t = std::max(0.0f, std::min(1.0f, t)); // clamp t to be between 0->1
int intervals = pnts - 1; // the intervals are the segments between each point
int n = std::max(0, std::min(pnts-1, int(intervals * t))); // figure out which interval t is between
t = t * intervals - n; // the new t value is the ratio between the points at n and n+1
float tmp1 = 0.5f * (1.0f - a_tension);
float A = tmp1 * (1.0f + a_continuity) * (1.0f + a_bias); // A,B,C,D are the parameter factors
float B = tmp1 * (1.0f - a_continuity) * (1.0f - a_bias);
float C = tmp1 * (1.0f - a_continuity) * (1.0f + a_bias);
float D = tmp1 * (1.0f + a_continuity) * (1.0f - a_bias);
const vec2f& pnt0 = a_points[std::max(n - 1, 0)];
const vec2f& pnt1 = a_points[n];
const vec2f& pnt2 = a_points[std::min(pnts - 1, n + 1)];
const vec2f& pnt3 = a_points[std::min(pnts - 1, n + 2)];
vec2f vA = -A*pnt0 + (2+A-B-C)*pnt1 + (-2+B+C-D)*pnt2 + D*pnt3; // vA,vB,vC - polynomial factors
vec2f vB = 2*A*pnt0 + (-3-2*A+2*B+C)*pnt1 + (3-2*B-C+D)*pnt2 + -D*pnt3;
vec2f vC = -A*pnt0 + (A-B)*pnt1 + B*pnt2;
return ((vA*t + vB)*t + vC)*t + pnt1; // blends by the factors between the 4 closest control points
}
vec2f catmulRomSpline(const std::vector<vec2f>& a_points, float t)
{
return kochanekBartelsSpline(a_points, t, 0.0f, 0.0f, 0.0f);
}
vec2f cubicSpline(const std::vector<vec2f>& a_points, float t)
{
return kochanekBartelsSpline(a_points, t, 1.0f, 0.0f, 0.0f);
}
vec2f lineSpline(const std::vector<vec2f>& a_points, float t)
{
return kochanekBartelsSpline(a_points, t, 0.0f, 0.0f, -1.0f);
}
*/
END_NAMESPACE