#include <cmath>
#include "Maths.h"
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
}
#if 0
struct vec4f
{
union {
float v[4];
struct { float x, y, z, w; };
};
};
struct mat4f
{
union {
vec4f m[4];
struct { vec4f t, b, n, w; }; // tangent, bi-normal, normal
};
};
vec4f operator +(const vec4f& a, const vec4f& b)
{
vec4f res = {{{ a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w }}};
return res;
}
vec4f operator -(const vec4f& a, const vec4f& b)
{
vec4f res = {{{ a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w }}};
return res;
}
inline float dot3(const vec4f& a, const vec4f& b)
{
//return Math::dotProduct(&a.x, &b.x, 1, 1, 3);
return a.x * b.x + a.y * b.y + a.z * b.z;
}
vec4f operator *(const vec4f& a, const vec4f& b)
{
vec4f res;
res.x = a.x * b.x;
res.y = a.y * b.y;
res.z = a.z * b.z;
res.w = a.w * b.w;
return res;
}
vec4f scaleVector(const vec4f& a, float b)
{
vec4f res;
res.x = a.x * b;
res.y = a.y * b;
res.z = a.z * b;
res.w = a.w * b;
return res;
}
vec4f operator *(const vec4f& a, float b)
{
return scaleVector(a, b);
}
struct rotation
{
const vec4f& q() // get the quaternion
{
return quaternion;
}
const mat4f& m() // get as a matrix
{
if (dirty)
{
convert();
}
return cachedMatrix;
}
void set(vec4f quat) // set
{
quaternion = quat;
dirty = true;
}
// TODO: perhaps operator overload the get and sets
private:
void convert()
{
Math::quatToMatrix4x4(&cachedMatrix.m[0].x, &quaternion.x);
dirty = false;
}
vec4f quaternion;
bool dirty = true;
mat4f cachedMatrix; // only needs to be 3x3
};
vec4f rotateVector(const vec4f& vec, const mat4f& mat)
{
vec4f res;
Math::transformVector(&res.x, &mat.t.x, &vec.x);
return res;
}
vec4f operator *(const vec4f& a, const mat4f& mat)
{
return rotateVector(a, mat);
}
float vectorLength(vec4f& vec)
{
return sqrt(Math::dotProduct(&vec.x, &vec.x, 1, 1, 3));
}
#include <OpenGL/gl.h> // suggest not include directly but make the framework provide an include which includes the system one
#endif
#include "Widget.h"
#include "CommonWidgets.h"
#include "Painter.h"
#include "Application.h"
USING_NAMESPACE
class NamedSlider : public HBox
{
public:
NamedSlider(Widget* parent, Property<int>& value, const char* name) : HBox(parent)
{
label = name;
new Label(this, label);
new Slider(this, value);
}
Property<String> label;
};
class FontEditWindow : public Widget
{
VBox vbox;
Button quitButton;
Property<bool> useRoundPen;
Property<bool> useSquarePen;
Property<bool> useFlatPen;
Property<int> bias;
Property<int> continuity;
Property<int> tension;
Property<int> ascent;
Property<int> descent;
Property<int> angle;
Property<int> width;
Property<int> penAngle;
Property<int> penWidth;
Property<int> character;
public:
FontEditWindow(Widget* a_parent, const char* a_name)
: Widget(a_parent, a_name)
, vbox(this)
, quitButton(&vbox, "Quit")
{
new CheckBox(&vbox, useRoundPen, "Use Round Pen");
new CheckBox(&vbox, useSquarePen, "Use Square Pen");
new CheckBox(&vbox, useFlatPen, "Use Flat Pen");
Grid *g = new Grid(&vbox, 2);
new GridSlider(g, bias, "Bias");
new GridSlider(g, continuity, "Continuity");
new GridSlider(g, tension, "Tension");
new GridSlider(g, ascent, "Ascender Height");
new GridSlider(g, descent, "Descender Height");
new GridSlider(g, angle, "Angle");
new GridSlider(g, width, "Width");
new GridSlider(g, penAngle, "Pen Angle");
new GridSlider(g, penWidth, "Pen Width");
new GridSlider(g, character, "Character");
connect(quitButton.activated, this, &FontEditWindow::onQuit);
bias = (1<<15);
tension = (1<<15);
continuity = (1<<16);
}
void onQuit(int)
{
exit(0);
}
void mouseEvent(MouseEvent& a_event) override
{
/*
int x = a_event.m_position.m_x;
int y = a_event.m_position.m_y;
if (lastX == -1) { lastX = x; lastY = y; }
if (a_event.m_buttons == MB_Right)
{
tx += x - lastX; ty += y - lastY;
}
else if (a_event.m_buttons == MB_Left)
{
trackingMouse = !trackingMouse;
x = -1;
}
else if (trackingMouse)
{
rx += x - lastX; ry += y - lastY;
}
else
{
x = -1;
}
lastX = x; lastY = y;
*/
//printf("got mouse event\n");
repaint();
}
void paintEvent(PaintEvent& a_event) override
{
Painter p(this);
p.setPen(0x000000);
int w = width() / 2;
int h = height() / 2;
p.drawLine(5, 5, w-5, 5);
p.drawLine(5, 5, 5, h-5);
p.drawLine(w-5, 5, w-5, h-5);
p.drawLine(5, h-5, w-5, h-5);
//repaint();
int scale = 50;
int x1 = 350;
int y1 = 100;
int penWidth = 7;
int xstep = scale;
int ystep = scale;
int w2 = ystep*3;
int h2 = xstep*3;
for (int i = 0; i < 4; i++)
p.drawLine(x1, y1+ystep*i, x1+w2, y1+ystep*i);
for (int i = 0; i < 4; i++)
p.drawLine(x1+xstep*i, y1, x1+xstep*i, y1+h2);
float cx = x1 + xstep + (xstep - penWidth) / 2;
float cy = y1 + ystep * 1.5f;
// For each stroke, quantize the points to a 16x16 grid - a position can be encoded in 8-bits
// Then the composition of the strokes can place these stoke pieces relative to each other with
// some positioning flags - v-flip, h-flip, left/top offset of 4bits each
// ---- ---- ---- ----
// ---- ---- ---- ----
// ---- ---- ---- ----
// ---- ---- ---- ----
// ---- ---- ---- ----
// Editor of the strokes with snapping to grid and with a box pen position so snapping can be aligned according to the pen and
// the grid points
std::vector<vec2f> pnts[16];
// C-top
pnts[0].push_back((vec2f){ float(x1 + xstep), cy });
pnts[0].push_back((vec2f){ float(x1 + xstep) + 5, cy - 10 });
pnts[0].push_back((vec2f){ cx - 10, float(y1 + ystep + 5) });
pnts[0].push_back((vec2f){ cx, float(y1 + ystep) });
pnts[0].push_back((vec2f){ cx + 10, float(y1 + ystep + 5) });
pnts[0].push_back((vec2f){ float(x1 + 2*xstep - penWidth) - 5, cy - 15 });
pnts[0].push_back((vec2f){ float(x1 + 2*xstep - penWidth), cy - 5 });
// a-line
pnts[1].push_back((vec2f){ float(x1 + 2*xstep - penWidth), float(y1 + ystep + 2) });
pnts[1].push_back((vec2f){ float(x1 + 2*xstep - penWidth), float(y1 + 2*ystep + 2) });
// b-line
pnts[2].push_back((vec2f){ float(x1 + 2*xstep - penWidth), float(y1 + 2) });
pnts[2].push_back((vec2f){ float(x1 + 2*xstep - penWidth), float(y1 + 2*ystep + 2) });
// e-line
pnts[3].push_back((vec2f){ float(x1 + xstep + penWidth), float(cy) });
pnts[3].push_back((vec2f){ float(x1 + 2*xstep - penWidth), float(cy) });
// n-line
pnts[4].push_back((vec2f){ float(x1 + 2*xstep - penWidth), float(cy) });
pnts[4].push_back((vec2f){ float(x1 + 2*xstep - penWidth), float(y1 + 2*ystep + 2) });
// n-line
pnts[4].push_back((vec2f){ float(x1 + 2*xstep - penWidth), float(cy) });
pnts[4].push_back((vec2f){ float(x1 + 2*xstep - penWidth), float(y1 + 2*ystep + 2) });
/*
int pw = 2;
std::vector<unsigned char> pnts2[16];
// C-top
pnts2[0].push_back((vec2f){ 0, 8 });
pnts2[0].push_back((vec2f){ 0 + 1, 8 - 1 });
pnts2[0].push_back((vec2f){ 8 - 1, 0 + 1 });
pnts2[0].push_back((vec2f){ 8, 0 });
pnts2[0].push_back((vec2f){ 8 + 1, 0 + 1 });
pnts2[0].push_back((vec2f){ (16 - pw) - 1, 8 - 2 });
pnts2[0].push_back((vec2f){ 16 - pw, 8 - 1 });
// a-line
pnts2[1].push_back((vec2f){ 16 - pw, 5 });
pnts2[1].push_back((vec2f){ 16 - pw, 10 });
// b-line
pnts2[2].push_back((vec2f){ 16 - pw, 0 });
pnts2[2].push_back((vec2f){ 16 - pw, 16 });
// e-line
pnts2[3].push_back((vec2f){ 8 + pw, 4 });
pnts2[3].push_back((vec2f){ 16 - pw, 4 });
// n-line
pnts2[4].push_back((vec2f){ 16 - pw, float(cy) });
pnts2[4].push_back((vec2f){ 16 - pw, float(y1 + 2*ystep + 2) });
// n-line
pnts2[4].push_back((vec2f){ 16 - pw, float(cy) });
pnts2[4].push_back((vec2f){ 16 - pw, float(y1 + 2*ystep + 2) });
// pnts[0] is c-top
// pnts[0] flipped vertically is c-bottom
// pnts[1] is a-line
// pnts[2] is b-line
*/
// a -> 0,c-top 1,c-top 0,a-line
struct { int flags; int index; } characters[][5] = {
{ { 0|0, 0 }, { 0|1, 0 }, { 0|0, 1 }, { -1, -1 } }, // 'a'
{ { 2|0, 0 }, { 2|1, 0 }, { 2|0, 2 }, { -1, -1 } }, // 'b'
{ { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } }, // 'c'
{ { 0|0, 0 }, { 0|1, 0 }, { 0|0, 2 }, { -1, -1 } }, // 'd'
{ { 0|0, 0 }, { 0|1, 0 }, { 0|0, 3 }, { -1, -1 } }, // 'e'
{ { 0|0, 3 }, { 4|0, 2 }, { -1, -1 } }, // 'c' 'f'
{ { 0|0, 0 }, { 0|1, 0 }, { 4|2|0, 2 }, { -1, -1 } },// 'c' 'g'
{ { 2|0, 0 }, { 0|0, 4 }, { 2|0, 2 }, { -1, -1 } }, // 'b' 'h'
{ { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } }, // 'c' 'i'
{ { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } }, // 'c' 'j'
{ { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } }, // 'c' 'k'
{ { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } }, // 'c' 'l'
{ { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } }, // 'c' 'm'
{ { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } }, // 'c' 'n'
{ { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } }, // 'c' 'o'
{ { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } }, // 'c' 'p'
{ { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } }, // 'c' 'q'
{ { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } }, // 'c' 'r'
{ { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } }, // 'c' 's'
{ { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } }, // 'c' 't'
{ { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } }, // 'c' 'u'
{ { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } }, // 'c' 'v'
{ { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } }, // 'c' 'w'
{ { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } }, // 'c' 'x'
{ { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } }, // 'c' 'y'
{ { 0|0, 0 }, { 0|1, 0 }, { -1, -1 } }, // 'c' 'z'
};
int ch = 'a' + ((character.value() * 26) >> 16);
int idx = ch - 'a';
for (int i = 0; i < 5; i++)
{
int flags = characters[idx][i].flags;
int index = characters[idx][i].index;
if (flags == -1)
break;
drawCurve(p, applyFlags(flags, pnts[index], cx, cy), penWidth);
}
}
std::vector<vec2f> applyFlags(int flags, std::vector<vec2f>& pnts, float cx, float cy)
{
std::vector<vec2f> out = pnts;
if ( flags & 1 )
{
// Flip/mirror vertically
for (vec2f& pt : out)
{
pt.y = 2*cy - pt.y;
}
}
if ( flags & 2 )
{
// Flip/mirror horizontally
for (vec2f& pt : out)
{
pt.x = 2*cx - pt.x;
}
}
return out;
}
void drawCurve(Painter& p, const std::vector<vec2f>& pnts, int penWidth)
{
float t = (tension.value() - (1<<15)) / float(1<<15);
float b = (bias.value() - (1<<15)) / float(1<<15);
float c = (continuity.value() - (1<<15)) / float(1<<15);
for (int i = 0; i < 100; i++)
{
vec2f kochanekBartelsSpline(const std::vector<vec2f>& a_points, float t, float a_tension, float a_bias, float a_continuity);
vec2f p1 = kochanekBartelsSpline(pnts, (i-0.5f) * 0.01f, t, b, c);
vec2f p2 = kochanekBartelsSpline(pnts, (i+0.5f) * 0.01f, t, b, c);
p.drawLine(int(p1.x), int(p1.y), int(p2.x), int(p2.y));
p.drawLine(int(p1.x) + penWidth, int(p1.y) - 3, int(p2.x) + penWidth, int(p2.y) - 3);
}
}
private:
//bool trackingMouse = false;
//int tx = 0, ty = 0, tz = -120;
//int rx = 0, ry = 0, rz = 0;
//int lastX = -1, lastY = -1;
};
void fontEditTest()
{
Application app;
FontEditWindow testWin(0, "Test Window");
testWin.setNewSize(800, 600);
app.exec();
exit(0);
}