406 lines
11 KiB
C++
406 lines
11 KiB
C++
#include <SDL_opengl.h>
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <assert.h>
|
|
#include <map>
|
|
#include <ft2build.h>
|
|
#include "glfreetype.h"
|
|
|
|
#include FT_FREETYPE_H
|
|
#include FT_GLYPH_H
|
|
#include FT_OUTLINE_H
|
|
|
|
#ifdef _WIN32
|
|
typedef GLvoid(APIENTRY* _GLUfuncptr)(void);
|
|
#endif
|
|
|
|
FT_Library library;
|
|
|
|
#include <vector>
|
|
|
|
static GLUtesselator* tobj;
|
|
|
|
static inline double fac(int n) {
|
|
double r = 1.0;
|
|
|
|
for(int i = 2; i <= n; i++) {
|
|
r *= (double)i;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
static inline double binomial_coeff(int n, int m) {
|
|
return fac(n)/(fac(m)*(fac(n-m)));
|
|
}
|
|
|
|
static void eval_bezier(GLdouble* out, double t, int n_points, double* points) {
|
|
std::vector<double> c(n_points);
|
|
|
|
for(int i = 0; i < n_points; i++) {
|
|
c[i] = pow(1.0f-t, n_points-(i+1)) * pow(t, i) *
|
|
binomial_coeff(n_points-1, i);
|
|
}
|
|
|
|
out[0] = out[1] = out[2] = 0;
|
|
|
|
for(int i = 0; i < n_points; i++) {
|
|
out[0] += points[3*i] * c[i];
|
|
out[1] += points[3*i+1] * c[i];
|
|
out[2] += points[3*i+2] * c[i];
|
|
}
|
|
}
|
|
|
|
#define DIV 2048.0f
|
|
|
|
bool GenContourPoints(int a_char, FT_Outline* a_outline, const int a_contour,
|
|
int a_bezierIters, std::vector<double>* ao_points) {
|
|
#define push_point(__p) { \
|
|
ao_points->push_back((__p)[0]); \
|
|
ao_points->push_back((__p)[1]); \
|
|
ao_points->push_back((__p)[2]); \
|
|
}
|
|
|
|
int cont = (a_contour-1 < 0 ? 0 : 1 + a_outline->contours[a_contour-1]);
|
|
|
|
double point_buf[256][3];
|
|
char point_type[256];
|
|
int pos = 0;
|
|
|
|
for(; cont <= a_outline->contours[a_contour]; cont++, pos++) {
|
|
point_type[pos] = a_outline->tags[cont];
|
|
point_buf[pos][0] = a_outline->points[cont].x/DIV;
|
|
point_buf[pos][1] = a_outline->points[cont].y/DIV;
|
|
point_buf[pos][2] = 0;
|
|
}
|
|
if(!point_type[pos-1]) {
|
|
/*
|
|
* Need to duplicate first vertex if last
|
|
* section is a bezier.
|
|
*/
|
|
point_type[pos] = 1;
|
|
point_buf[pos][0] = point_buf[0][0];
|
|
point_buf[pos][1] = point_buf[0][1];
|
|
point_buf[pos][2] = 0;
|
|
pos++;
|
|
}
|
|
|
|
int start = -1;
|
|
for(int k = 0; k < pos; k++) {
|
|
if(!(point_type[k] & 1)) continue;
|
|
|
|
if(start == -1) { start = k; continue; }
|
|
|
|
int len = 1+k-start;
|
|
/* Trace segment. */
|
|
if(len == 2) {
|
|
/* Staight line. */
|
|
push_point(point_buf[k-1]);
|
|
push_point(point_buf[k]);
|
|
} else {
|
|
/* Bezier. */
|
|
double b_in[3][3];
|
|
double v[3];
|
|
|
|
/*
|
|
* Truetype is all quadratic bezier,
|
|
* using average points between
|
|
* 'control points' as end points.
|
|
*/
|
|
|
|
/* First bezier. */
|
|
b_in[0][0] = point_buf[start][0];
|
|
b_in[0][1] = point_buf[start][1];
|
|
b_in[0][2] = 0;
|
|
|
|
b_in[1][0] = point_buf[start+1][0];
|
|
b_in[1][1] = point_buf[start+1][1];
|
|
b_in[1][2] = 0;
|
|
|
|
if(len > 3) {
|
|
b_in[2][0] = 0.5 * (point_buf[start+1][0] + point_buf[start+2][0]);
|
|
b_in[2][1] = 0.5 * (point_buf[start+1][1] + point_buf[start+2][1]);
|
|
b_in[2][2] = 0;
|
|
} else {
|
|
b_in[2][0] = point_buf[start+2][0];
|
|
b_in[2][1] = point_buf[start+2][1];
|
|
b_in[2][2] = 0;
|
|
}
|
|
|
|
for(int l = 0; l <= a_bezierIters; l++) {
|
|
double t = (1.0/a_bezierIters)*l;
|
|
eval_bezier(v, t, 3, &b_in[0][0]);
|
|
v[2] = 0.0;
|
|
push_point(v);
|
|
}
|
|
|
|
/* Middle beziers. */
|
|
if(len > 4) {
|
|
for(int _p = 1; _p < len-3; _p++) {
|
|
b_in[0][0] = 0.5*(point_buf[start+_p][0]+point_buf[start+_p+1][0]);
|
|
b_in[0][1] = 0.5*(point_buf[start+_p][1]+point_buf[start+_p+1][1]);
|
|
|
|
b_in[0][2] = 0;
|
|
|
|
b_in[1][0] = point_buf[start+_p+1][0];
|
|
b_in[1][1] = point_buf[start+_p+1][1];
|
|
b_in[1][2] = 0;
|
|
|
|
b_in[2][0] = 0.5*(point_buf[start+_p+1][0] + point_buf[start+_p+2][0]);
|
|
b_in[2][1] = 0.5*(point_buf[start+_p+1][1] + point_buf[start+_p+2][1]);
|
|
b_in[2][2] = 0;
|
|
|
|
for(int l = 0; l <= a_bezierIters; l++) {
|
|
double t = (1.0/a_bezierIters)*l;
|
|
eval_bezier(v, t, 3, &b_in[0][0]);
|
|
v[2] = 0.0;
|
|
push_point(v);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* End. */
|
|
if(len > 3) {
|
|
const int _p = start+len-3;
|
|
b_in[0][0] = 0.5 * (point_buf[_p][0] + point_buf[_p+1][0]);
|
|
b_in[0][1] = 0.5 * (point_buf[_p][1] + point_buf[_p+1][1]);
|
|
b_in[0][2] = 0;
|
|
|
|
b_in[1][0] = point_buf[_p+1][0];
|
|
b_in[1][1] = point_buf[_p+1][1];
|
|
b_in[1][2] = 0;
|
|
|
|
b_in[2][0] = point_buf[_p+2][0];
|
|
b_in[2][1] = point_buf[_p+2][1];
|
|
b_in[2][2] = 0;
|
|
|
|
for(int l = 0; l <= a_bezierIters; l++) {
|
|
double t = (1.0/a_bezierIters)*l;
|
|
eval_bezier(v, t, 3, &b_in[0][0]);
|
|
v[2] = 0.0;
|
|
push_point(v);
|
|
}
|
|
}
|
|
}
|
|
start = k;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#ifndef CALLBACK
|
|
# ifdef WIN32
|
|
# define CALLBACK __attribute__ ((__stdcall__))
|
|
# else
|
|
# define CALLBACK
|
|
# endif
|
|
#endif /* CALLBACK */
|
|
|
|
struct TessData {
|
|
std::vector<double>* pts; /* Inputs, added by combine. */
|
|
int numvtx;
|
|
|
|
std::vector<Uint16> index; /* Output index list. */
|
|
GLenum lasttype;
|
|
int state; /* 0, no vertices, 1, 1 vertex, 2, 2 or more. 0x4 => clockwise. */
|
|
|
|
Uint16 vtx[2];
|
|
};
|
|
|
|
static Uint16 g_index[65536];
|
|
|
|
void CALLBACK beginCallback(GLenum which, GLvoid* poly_data) {
|
|
TessData* pData = (TessData*)poly_data;
|
|
pData->lasttype = which;
|
|
pData->state = 0;
|
|
}
|
|
|
|
void CALLBACK errorCallback(GLenum errorCode) {
|
|
const GLubyte* estr;
|
|
|
|
estr = gluErrorString(errorCode);
|
|
fprintf(stderr, "Tesserlation Error: %s\n", estr);
|
|
}
|
|
|
|
void CALLBACK endCallback(void) {
|
|
|
|
}
|
|
|
|
void CALLBACK vertexCallback(GLvoid* vertex, GLvoid* poly_data) {
|
|
TessData* pData = (TessData*)poly_data;
|
|
Uint16 index = *(Uint16*)vertex;
|
|
switch(pData->lasttype) {
|
|
case GL_TRIANGLES:
|
|
pData->index.push_back(index);
|
|
break;
|
|
case GL_TRIANGLE_STRIP:
|
|
if((pData->state & 3) < 2)
|
|
pData->vtx[pData->state++] = index;
|
|
else {
|
|
pData->index.push_back(index);
|
|
if(pData->state & 0x4) {
|
|
pData->index.push_back(pData->vtx[1]);
|
|
pData->index.push_back(pData->vtx[0]);
|
|
} else {
|
|
pData->index.push_back(pData->vtx[0]);
|
|
pData->index.push_back(pData->vtx[1]);
|
|
}
|
|
pData->vtx[0] = pData->vtx[1];
|
|
pData->vtx[1] = index;
|
|
pData->state ^= 0x4;
|
|
}
|
|
break;
|
|
case GL_TRIANGLE_FAN:
|
|
if((pData->state & 3) < 2)
|
|
pData->vtx[pData->state++] = index;
|
|
else {
|
|
pData->index.push_back(index);
|
|
pData->index.push_back(pData->vtx[0]);
|
|
pData->index.push_back(pData->vtx[1]);
|
|
pData->vtx[1] = index;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CALLBACK combineCallback(GLdouble coords[3],
|
|
GLdouble* vertex_data[4],
|
|
GLfloat weight[4], void** dataOut, void* poly_data) {
|
|
|
|
TessData* pData = (TessData*)poly_data;
|
|
pData->pts->push_back(coords[0]);
|
|
pData->pts->push_back(coords[1]);
|
|
pData->pts->push_back(coords[2]);
|
|
*dataOut = (void*)&g_index[pData->numvtx++];
|
|
}
|
|
|
|
#define BEZIER_STEPS 2
|
|
|
|
void FontFace::RenderGlyph(int chr) {
|
|
glEnableClientState(GL_VERTEX_ARRAY);
|
|
glDisableClientState(GL_NORMAL_ARRAY);
|
|
|
|
glfglyph_t* glyph = &m_glyphs[chr];
|
|
glVertexPointer(3, GL_FLOAT, 3*sizeof(float), glyph->varray);
|
|
glDrawElements(GL_TRIANGLES, glyph->numidx, GL_UNSIGNED_SHORT, glyph->iarray);
|
|
}
|
|
|
|
void FontFace::RenderString(const char* str) {
|
|
glPushMatrix();
|
|
for(unsigned int i = 0; i < strlen(str); i++) {
|
|
if(str[i] == '\n') {
|
|
glPopMatrix();
|
|
glTranslatef(0, -m_height, 0);
|
|
glPushMatrix();
|
|
} else {
|
|
glfglyph_t* glyph = &m_glyphs[str[i]];
|
|
if(glyph->numidx) RenderGlyph(str[i]);
|
|
glTranslatef(glyph->advx, 0, 0);
|
|
}
|
|
}
|
|
glPopMatrix();
|
|
}
|
|
|
|
/* 'Markup' indeed. #rgb hex is change colour, no sensible escape. */
|
|
void FontFace::RenderMarkup(const char* str) {
|
|
glPushMatrix();
|
|
int len = strlen(str);
|
|
for(int i = 0; i < len; i++) {
|
|
if(str[i] == '#') {
|
|
int hexcol;
|
|
if(sscanf(str+i, "#%3x", &hexcol)==1) {
|
|
Uint8 col[3];
|
|
col[0] = (hexcol&0xf00)>>4;
|
|
col[1] = (hexcol&0xf0);
|
|
col[2] = (hexcol&0xf)<<4;
|
|
glColor3ubv(col);
|
|
i+=3;
|
|
continue;
|
|
}
|
|
}
|
|
if(str[i] == '\n') {
|
|
glPopMatrix();
|
|
glTranslatef(0, -m_height, 0);
|
|
glPushMatrix();
|
|
} else {
|
|
glfglyph_t* glyph = &m_glyphs[str[i]];
|
|
if(glyph->numidx) RenderGlyph(str[i]);
|
|
glTranslatef(glyph->advx, 0, 0);
|
|
}
|
|
}
|
|
glPopMatrix();
|
|
}
|
|
|
|
FontFace::FontFace(const char* filename_ttf) {
|
|
FT_Face face;
|
|
int err;
|
|
if(0 != (err = FT_New_Face(library, filename_ttf, 0, &face))) {
|
|
fprintf(stderr, "Terrible error! Couldn't load '%s'; error %d.\n", filename_ttf, err);
|
|
} else {
|
|
FT_Set_Char_Size(face, 50*64, 0, 100, 0);
|
|
for(int chr = 32; chr < 127; chr++) {
|
|
if(0 != FT_Load_Char(face, chr, FT_LOAD_NO_SCALE)) {
|
|
printf("Couldn't load glyph\n");
|
|
continue;
|
|
}
|
|
|
|
assert(face->glyph->format == FT_GLYPH_FORMAT_OUTLINE);
|
|
FT_Outline* outline = &face->glyph->outline;
|
|
|
|
std::vector<double> temppts;
|
|
std::vector<Uint16> indices;
|
|
std::vector<double> pts;
|
|
int nv = 0;
|
|
|
|
TessData tessdata;
|
|
tessdata.pts = &pts;
|
|
|
|
gluTessNormal(tobj, 0, 0, 1);
|
|
gluTessProperty(tobj, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_ODD);
|
|
gluTessBeginPolygon(tobj, &tessdata);
|
|
for(int contour = 0; contour < outline->n_contours; contour++) {
|
|
gluTessBeginContour(tobj);
|
|
temppts.clear();
|
|
GenContourPoints(chr, outline, contour, BEZIER_STEPS, &temppts);
|
|
for(size_t i = 0; i < temppts.size(); i++) pts.push_back(temppts[i]);
|
|
for(size_t i = 0; i < temppts.size(); i+=3, nv++)
|
|
gluTessVertex(tobj, &pts[nv*3], &g_index[nv]);
|
|
gluTessEndContour(tobj);
|
|
}
|
|
tessdata.numvtx = nv;
|
|
gluTessEndPolygon(tobj);
|
|
|
|
glfglyph_t _face;
|
|
|
|
nv = tessdata.numvtx;
|
|
_face.varray = (float*)malloc(nv*3*sizeof(float));
|
|
for(int i = 0; i < nv*3; i++) _face.varray[i] = (float)pts[i];
|
|
|
|
_face.numidx = (int)tessdata.index.size();
|
|
_face.iarray = (Uint16*)malloc(_face.numidx*sizeof(Uint16));
|
|
for(int i = 0; i < _face.numidx; i++) _face.iarray[i] = tessdata.index[i];
|
|
|
|
_face.advx = face->glyph->linearHoriAdvance/(float)(1<<16)/72.0f;
|
|
_face.advy = face->glyph->linearVertAdvance/(float)(1<<16)/72.0f;
|
|
//printf(%f,%f\n", _face.advx, _face.advy);
|
|
m_glyphs[chr] = _face;
|
|
}
|
|
m_height = m_glyphs['M'].advy;
|
|
m_width = m_glyphs['M'].advx;
|
|
}
|
|
}
|
|
|
|
void GLFTInit(void) {
|
|
if(0 != FT_Init_FreeType(&library)) {
|
|
printf("Couldn't init freetype library.\n");
|
|
exit(0);
|
|
}
|
|
|
|
tobj = gluNewTess();
|
|
gluTessCallback(tobj, GLU_TESS_VERTEX_DATA, (_GLUfuncptr) vertexCallback);
|
|
gluTessCallback(tobj, GLU_TESS_BEGIN_DATA, (_GLUfuncptr) beginCallback);
|
|
gluTessCallback(tobj, GLU_TESS_END, (_GLUfuncptr) endCallback);
|
|
gluTessCallback(tobj, GLU_TESS_ERROR, (_GLUfuncptr) errorCallback);
|
|
gluTessCallback(tobj, GLU_TESS_COMBINE_DATA, (_GLUfuncptr) combineCallback);
|
|
|
|
for(Uint16 i = 0; i < 65535; i++) g_index[i] = i;
|
|
}
|