solvespace/src/glhelper.cpp
2016-05-25 03:22:54 +00:00

821 lines
25 KiB
C++

//-----------------------------------------------------------------------------
// Helper functions that ultimately draw stuff with gl.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#include "solvespace.h"
namespace SolveSpace {
static bool ColorLocked;
static bool DepthOffsetLocked;
void ssglLineWidth(GLfloat width) {
// Intel GPUs with Mesa on *nix render thin lines poorly.
static bool workaroundChecked, workaroundEnabled;
if(!workaroundChecked) {
// ssglLineWidth can be called before GL is initialized
if(glGetString(GL_VENDOR)) {
workaroundChecked = true;
if(!strcmp((char*)glGetString(GL_VENDOR), "Intel Open Source Technology Center"))
workaroundEnabled = true;
}
}
if(workaroundEnabled && width < 1.6f)
width = 1.6f;
glLineWidth(width);
}
static void LineDrawCallback(void *fndata, Vector a, Vector b)
{
ssglLineWidth(1);
glBegin(GL_LINES);
ssglVertex3v(a);
ssglVertex3v(b);
glEnd();
}
void ssglVertex3v(Vector u)
{
glVertex3f((GLfloat)u.x, (GLfloat)u.y, (GLfloat)u.z);
}
void ssglAxisAlignedQuad(double l, double r, double t, double b, bool lone)
{
if(lone) glBegin(GL_QUADS);
glVertex2d(l, t);
glVertex2d(l, b);
glVertex2d(r, b);
glVertex2d(r, t);
if(lone) glEnd();
}
void ssglAxisAlignedLineLoop(double l, double r, double t, double b)
{
glBegin(GL_LINE_LOOP);
glVertex2d(l, t);
glVertex2d(l, b);
glVertex2d(r, b);
glVertex2d(r, t);
glEnd();
}
static void FatLineEndcap(Vector p, Vector u, Vector v)
{
// A table of cos and sin of (pi*i/10 + pi/2), as i goes from 0 to 10
static const double Circle[11][2] = {
{ 0.0000, 1.0000 },
{ -0.3090, 0.9511 },
{ -0.5878, 0.8090 },
{ -0.8090, 0.5878 },
{ -0.9511, 0.3090 },
{ -1.0000, 0.0000 },
{ -0.9511, -0.3090 },
{ -0.8090, -0.5878 },
{ -0.5878, -0.8090 },
{ -0.3090, -0.9511 },
{ 0.0000, -1.0000 },
};
glBegin(GL_TRIANGLE_FAN);
for(int i = 0; i <= 10; i++) {
double c = Circle[i][0], s = Circle[i][1];
ssglVertex3v(p.Plus(u.ScaledBy(c)).Plus(v.ScaledBy(s)));
}
glEnd();
}
void ssglLine(const Vector &a, const Vector &b, double pixelWidth, bool maybeFat) {
if(!maybeFat || pixelWidth <= 3.0) {
glBegin(GL_LINES);
ssglVertex3v(a);
ssglVertex3v(b);
glEnd();
} else {
ssglFatLine(a, b, pixelWidth / SS.GW.scale);
}
}
void ssglPoint(Vector p, double pixelSize)
{
if(/*!maybeFat || */pixelSize <= 3.0) {
glBegin(GL_LINES);
Vector u = SS.GW.projRight.WithMagnitude(pixelSize / SS.GW.scale / 2.0);
ssglVertex3v(p.Minus(u));
ssglVertex3v(p.Plus(u));
glEnd();
} else {
Vector u = SS.GW.projRight.WithMagnitude(pixelSize / SS.GW.scale / 2.0);
Vector v = SS.GW.projUp.WithMagnitude(pixelSize / SS.GW.scale / 2.0);
FatLineEndcap(p, u, v);
FatLineEndcap(p, u.ScaledBy(-1.0), v);
}
}
void ssglStippledLine(Vector a, Vector b, double width,
int stippleType, double stippleScale, bool maybeFat)
{
const char *stipplePattern;
switch(stippleType) {
case Style::STIPPLE_CONTINUOUS: ssglLine(a, b, width, maybeFat); return;
case Style::STIPPLE_DASH: stipplePattern = "- "; break;
case Style::STIPPLE_LONG_DASH: stipplePattern = "_ "; break;
case Style::STIPPLE_DASH_DOT: stipplePattern = "-."; break;
case Style::STIPPLE_DASH_DOT_DOT: stipplePattern = "-.."; break;
case Style::STIPPLE_DOT: stipplePattern = "."; break;
case Style::STIPPLE_FREEHAND: stipplePattern = "~"; break;
case Style::STIPPLE_ZIGZAG: stipplePattern = "~__"; break;
default: ssassert(false, "Unexpected stipple pattern");
}
ssglStippledLine(a, b, width, stipplePattern, stippleScale, maybeFat);
}
void ssglStippledLine(Vector a, Vector b, double width,
const char *stipplePattern, double stippleScale, bool maybeFat)
{
ssassert(stipplePattern != NULL, "Unexpected stipple pattern");
Vector dir = b.Minus(a);
double len = dir.Magnitude();
dir = dir.WithMagnitude(1.0);
const char *si = stipplePattern;
double end = len;
double ss = stippleScale / 2.0;
do {
double start = end;
switch(*si) {
case ' ':
end -= 1.0 * ss;
break;
case '-':
start = max(start - 0.5 * ss, 0.0);
end = max(start - 2.0 * ss, 0.0);
if(start == end) break;
ssglLine(a.Plus(dir.ScaledBy(start)), a.Plus(dir.ScaledBy(end)), width, maybeFat);
end = max(end - 0.5 * ss, 0.0);
break;
case '_':
end = max(end - 4.0 * ss, 0.0);
ssglLine(a.Plus(dir.ScaledBy(start)), a.Plus(dir.ScaledBy(end)), width, maybeFat);
break;
case '.':
end = max(end - 0.5 * ss, 0.0);
if(end == 0.0) break;
ssglPoint(a.Plus(dir.ScaledBy(end)), width);
end = max(end - 0.5 * ss, 0.0);
break;
case '~': {
Vector ab = b.Minus(a);
Vector gn = (SS.GW.projRight).Cross(SS.GW.projUp);
Vector abn = (ab.Cross(gn)).WithMagnitude(1);
abn = abn.Minus(gn.ScaledBy(gn.Dot(abn)));
double pws = 2.0 * width / SS.GW.scale;
end = max(end - 0.5 * ss, 0.0);
Vector aa = a.Plus(dir.ScaledBy(start));
Vector bb = a.Plus(dir.ScaledBy(end))
.Plus(abn.ScaledBy(pws * (start - end) / (0.5 * ss)));
ssglLine(aa, bb, width, maybeFat);
if(end == 0.0) break;
start = end;
end = max(end - 1.0 * ss, 0.0);
aa = a.Plus(dir.ScaledBy(end))
.Plus(abn.ScaledBy(pws))
.Minus(abn.ScaledBy(2.0 * pws * (start - end) / ss));
ssglLine(bb, aa, width, maybeFat);
if(end == 0.0) break;
start = end;
end = max(end - 0.5 * ss, 0.0);
bb = a.Plus(dir.ScaledBy(end))
.Minus(abn.ScaledBy(pws))
.Plus(abn.ScaledBy(pws * (start - end) / (0.5 * ss)));
ssglLine(aa, bb, width, maybeFat);
break;
}
default: ssassert(false, "Unexpected stipple pattern element");
}
if(*(++si) == 0) si = stipplePattern;
} while(end > 0.0);
}
void ssglFatLine(Vector a, Vector b, double width)
{
if(a.EqualsExactly(b)) return;
// The half-width of the line we're drawing.
double hw = width / 2;
Vector ab = b.Minus(a);
Vector gn = (SS.GW.projRight).Cross(SS.GW.projUp);
Vector abn = (ab.Cross(gn)).WithMagnitude(1);
abn = abn.Minus(gn.ScaledBy(gn.Dot(abn)));
// So now abn is normal to the projection of ab into the screen, so the
// line will always have constant thickness as the view is rotated.
abn = abn.WithMagnitude(hw);
ab = gn.Cross(abn);
ab = ab. WithMagnitude(hw);
// The body of a line is a quad
glBegin(GL_QUADS);
ssglVertex3v(a.Minus(abn));
ssglVertex3v(b.Minus(abn));
ssglVertex3v(b.Plus (abn));
ssglVertex3v(a.Plus (abn));
glEnd();
// And the line has two semi-circular end caps.
FatLineEndcap(a, ab, abn);
FatLineEndcap(b, ab.ScaledBy(-1), abn);
}
void ssglLockColorTo(RgbaColor rgb)
{
ColorLocked = false;
glColor3d(rgb.redF(), rgb.greenF(), rgb.blueF());
ColorLocked = true;
}
void ssglUnlockColor()
{
ColorLocked = false;
}
void ssglColorRGB(RgbaColor rgb)
{
// Is there a bug in some graphics drivers where this is not equivalent
// to glColor3d? There seems to be...
ssglColorRGBa(rgb, 1.0);
}
void ssglColorRGBa(RgbaColor rgb, double a)
{
if(!ColorLocked) glColor4d(rgb.redF(), rgb.greenF(), rgb.blueF(), a);
}
static void Stipple(bool forSel)
{
static bool Init;
const int BYTES = (32*32)/8;
static GLubyte HoverMask[BYTES];
static GLubyte SelMask[BYTES];
if(!Init) {
int x, y;
for(x = 0; x < 32; x++) {
for(y = 0; y < 32; y++) {
int i = y*4 + x/8, b = x % 8;
int ym = y % 4, xm = x % 4;
for(int k = 0; k < 2; k++) {
if(xm >= 1 && xm <= 2 && ym >= 1 && ym <= 2) {
(k == 0 ? SelMask : HoverMask)[i] |= (0x80 >> b);
}
ym = (ym + 2) % 4; xm = (xm + 2) % 4;
}
}
}
Init = true;
}
glEnable(GL_POLYGON_STIPPLE);
if(forSel) {
glPolygonStipple(SelMask);
} else {
glPolygonStipple(HoverMask);
}
}
static void StippleTriangle(STriangle *tr, bool s, RgbaColor rgb)
{
glEnd();
glDisable(GL_LIGHTING);
ssglColorRGB(rgb);
Stipple(s);
glBegin(GL_TRIANGLES);
ssglVertex3v(tr->a);
ssglVertex3v(tr->b);
ssglVertex3v(tr->c);
glEnd();
glEnable(GL_LIGHTING);
glDisable(GL_POLYGON_STIPPLE);
glBegin(GL_TRIANGLES);
}
void ssglFillMesh(bool useSpecColor, RgbaColor specColor,
SMesh *m, uint32_t h, uint32_t s1, uint32_t s2)
{
RgbaColor rgbHovered = Style::Color(Style::HOVERED),
rgbSelected = Style::Color(Style::SELECTED);
glEnable(GL_NORMALIZE);
bool hasMaterial = false;
RgbaColor prevColor;
glBegin(GL_TRIANGLES);
for(int i = 0; i < m->l.n; i++) {
STriangle *tr = &(m->l.elem[i]);
RgbaColor color;
if(useSpecColor) {
color = specColor;
} else {
color = tr->meta.color;
}
if(!hasMaterial || !color.Equals(prevColor)) {
GLfloat mpf[] = { color.redF(), color.greenF(), color.blueF(), color.alphaF() };
glEnd();
glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, mpf);
prevColor = color;
hasMaterial = true;
glBegin(GL_TRIANGLES);
}
if(tr->an.EqualsExactly(Vector::From(0, 0, 0))) {
// Compute the normal from the vertices
Vector n = tr->Normal();
glNormal3d(n.x, n.y, n.z);
ssglVertex3v(tr->a);
ssglVertex3v(tr->b);
ssglVertex3v(tr->c);
} else {
// Use the exact normals that are specified
glNormal3d((tr->an).x, (tr->an).y, (tr->an).z);
ssglVertex3v(tr->a);
glNormal3d((tr->bn).x, (tr->bn).y, (tr->bn).z);
ssglVertex3v(tr->b);
glNormal3d((tr->cn).x, (tr->cn).y, (tr->cn).z);
ssglVertex3v(tr->c);
}
if((s1 != 0 && tr->meta.face == s1) ||
(s2 != 0 && tr->meta.face == s2))
{
StippleTriangle(tr, true, rgbSelected);
}
if(h != 0 && tr->meta.face == h) {
StippleTriangle(tr, false, rgbHovered);
}
}
glEnd();
}
static void SSGL_CALLBACK Vertex(Vector *p)
{
ssglVertex3v(*p);
}
void ssglFillPolygon(SPolygon *p)
{
GLUtesselator *gt = gluNewTess();
gluTessCallback(gt, GLU_TESS_BEGIN, (ssglCallbackFptr *)glBegin);
gluTessCallback(gt, GLU_TESS_END, (ssglCallbackFptr *)glEnd);
gluTessCallback(gt, GLU_TESS_VERTEX, (ssglCallbackFptr *)Vertex);
ssglTesselatePolygon(gt, p);
gluDeleteTess(gt);
}
static void SSGL_CALLBACK Combine(double coords[3], void *vertexData[4],
float weight[4], void **outData)
{
Vector *n = (Vector *)AllocTemporary(sizeof(Vector));
n->x = coords[0];
n->y = coords[1];
n->z = coords[2];
*outData = n;
}
void ssglTesselatePolygon(GLUtesselator *gt, SPolygon *p)
{
int i, j;
gluTessCallback(gt, GLU_TESS_COMBINE, (ssglCallbackFptr *)Combine);
gluTessProperty(gt, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_ODD);
Vector normal = p->normal;
glNormal3d(normal.x, normal.y, normal.z);
gluTessNormal(gt, normal.x, normal.y, normal.z);
gluTessBeginPolygon(gt, NULL);
for(i = 0; i < p->l.n; i++) {
SContour *sc = &(p->l.elem[i]);
gluTessBeginContour(gt);
for(j = 0; j < (sc->l.n-1); j++) {
SPoint *sp = &(sc->l.elem[j]);
double ap[3];
ap[0] = sp->p.x;
ap[1] = sp->p.y;
ap[2] = sp->p.z;
gluTessVertex(gt, ap, &(sp->p));
}
gluTessEndContour(gt);
}
gluTessEndPolygon(gt);
}
void ssglDebugPolygon(SPolygon *p)
{
int i, j;
ssglLineWidth(2);
glPointSize(7);
glDisable(GL_DEPTH_TEST);
for(i = 0; i < p->l.n; i++) {
SContour *sc = &(p->l.elem[i]);
for(j = 0; j < (sc->l.n-1); j++) {
Vector a = (sc->l.elem[j]).p;
Vector b = (sc->l.elem[j+1]).p;
ssglLockColorTo(RGBi(0, 0, 255));
Vector d = (a.Minus(b)).WithMagnitude(-0);
glBegin(GL_LINES);
ssglVertex3v(a.Plus(d));
ssglVertex3v(b.Minus(d));
glEnd();
ssglLockColorTo(RGBi(255, 0, 0));
glBegin(GL_POINTS);
ssglVertex3v(a.Plus(d));
ssglVertex3v(b.Minus(d));
glEnd();
}
}
}
void ssglDrawEdges(SEdgeList *el, bool endpointsToo, hStyle hs)
{
double lineWidth = Style::Width(hs);
int stippleType = Style::PatternType(hs);
double stippleScale = Style::StippleScaleMm(hs);
ssglLineWidth(float(lineWidth));
ssglColorRGB(Style::Color(hs));
SEdge *se;
for(se = el->l.First(); se; se = el->l.NextAfter(se)) {
ssglStippledLine(se->a, se->b, lineWidth, stippleType, stippleScale,
/*maybeFat=*/true);
}
if(endpointsToo) {
glPointSize(12);
glBegin(GL_POINTS);
for(se = el->l.First(); se; se = el->l.NextAfter(se)) {
ssglVertex3v(se->a);
ssglVertex3v(se->b);
}
glEnd();
}
}
void ssglDrawOutlines(SOutlineList *sol, Vector projDir, hStyle hs)
{
double lineWidth = Style::Width(hs);
int stippleType = Style::PatternType(hs);
double stippleScale = Style::StippleScaleMm(hs);
ssglLineWidth((float)lineWidth);
ssglColorRGB(Style::Color(hs));
for(SOutline *so = sol->l.First(); so; so = sol->l.NextAfter(so)) {
if(!so->IsVisible(projDir)) continue;
ssglStippledLine(so->a, so->b, lineWidth, stippleType, stippleScale,
/*maybeFat=*/true);
}
}
void ssglDebugMesh(SMesh *m)
{
int i;
ssglLineWidth(1);
glPointSize(7);
ssglDepthRangeOffset(1);
ssglUnlockColor();
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
ssglColorRGBa(RGBi(0, 255, 0), 1.0);
glBegin(GL_TRIANGLES);
for(i = 0; i < m->l.n; i++) {
STriangle *t = &(m->l.elem[i]);
if(t->tag) continue;
ssglVertex3v(t->a);
ssglVertex3v(t->b);
ssglVertex3v(t->c);
}
glEnd();
ssglDepthRangeOffset(0);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}
void ssglMarkPolygonNormal(SPolygon *p)
{
Vector tail = Vector::From(0, 0, 0);
int i, j, cnt = 0;
// Choose some reasonable center point.
for(i = 0; i < p->l.n; i++) {
SContour *sc = &(p->l.elem[i]);
for(j = 0; j < (sc->l.n-1); j++) {
SPoint *sp = &(sc->l.elem[j]);
tail = tail.Plus(sp->p);
cnt++;
}
}
if(cnt == 0) return;
tail = tail.ScaledBy(1.0/cnt);
Vector gn = SS.GW.projRight.Cross(SS.GW.projUp);
Vector tip = tail.Plus((p->normal).WithMagnitude(40/SS.GW.scale));
Vector arrow = (p->normal).WithMagnitude(15/SS.GW.scale);
glColor3d(1, 1, 0);
glBegin(GL_LINES);
ssglVertex3v(tail);
ssglVertex3v(tip);
ssglVertex3v(tip);
ssglVertex3v(tip.Minus(arrow.RotatedAbout(gn, 0.6)));
ssglVertex3v(tip);
ssglVertex3v(tip.Minus(arrow.RotatedAbout(gn, -0.6)));
glEnd();
glEnable(GL_LIGHTING);
}
void ssglDepthRangeOffset(int units)
{
if(!DepthOffsetLocked) {
// The size of this step depends on the resolution of the Z buffer; for
// a 16-bit buffer, this should be fine.
double d = units/60000.0;
glDepthRange(0.1-d, 1-d);
}
}
void ssglDepthRangeLockToFront(bool yes)
{
if(yes) {
DepthOffsetLocked = true;
glDepthRange(0, 0);
} else {
DepthOffsetLocked = false;
ssglDepthRangeOffset(0);
}
}
void ssglDrawPixmap(const Pixmap &pixmap, bool flip) {
glBindTexture(GL_TEXTURE_2D, TEXTURE_DRAW_PIXELS);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
int format = pixmap.hasAlpha ? GL_RGBA : GL_RGB;
glTexImage2D(GL_TEXTURE_2D, 0, format, pixmap.width, pixmap.height, 0,
format, GL_UNSIGNED_BYTE, &pixmap.data[0]);
glEnable(GL_TEXTURE_2D);
glBegin(GL_QUADS);
glTexCoord2d(0.0, flip ? 0.0 : 1.0);
glVertex2d(0.0, (double)pixmap.height);
glTexCoord2d(1.0, flip ? 0.0 : 1.0);
glVertex2d((double)pixmap.width, (double)pixmap.height);
glTexCoord2d(1.0, flip ? 1.0 : 0.0);
glVertex2d((double)pixmap.width, 0.0);
glTexCoord2d(0.0, flip ? 1.0 : 0.0);
glVertex2d(0.0, 0.0);
glEnd();
glDisable(GL_TEXTURE_2D);
}
//-----------------------------------------------------------------------------
// Bitmap font rendering
//-----------------------------------------------------------------------------
static BitmapFont BuiltinBitmapFont;
static void LoadBitmapFont() {
if(!BuiltinBitmapFont.IsEmpty()) return;
BuiltinBitmapFont = BitmapFont::From(LoadStringFromGzip("fonts/unifont.hex.gz"));
BuiltinBitmapFont.AddGlyph(0xE000, LoadPNG("fonts/private/0-check-false.png"));
BuiltinBitmapFont.AddGlyph(0xE001, LoadPNG("fonts/private/1-check-true.png"));
BuiltinBitmapFont.AddGlyph(0xE002, LoadPNG("fonts/private/2-radio-false.png"));
BuiltinBitmapFont.AddGlyph(0xE003, LoadPNG("fonts/private/3-radio-true.png"));
BuiltinBitmapFont.AddGlyph(0xE004, LoadPNG("fonts/private/4-stipple-dot.png"));
BuiltinBitmapFont.AddGlyph(0xE005, LoadPNG("fonts/private/5-stipple-dash-long.png"));
BuiltinBitmapFont.AddGlyph(0xE006, LoadPNG("fonts/private/6-stipple-dash.png"));
BuiltinBitmapFont.AddGlyph(0xE007, LoadPNG("fonts/private/7-stipple-zigzag.png"));
// Unifont doesn't have a glyph for U+0020.
BuiltinBitmapFont.AddGlyph(0x20, Pixmap({ 8, 16, 8*3, false, std::vector<uint8_t>(8*16*3) }));
}
void ssglInitializeBitmapFont()
{
LoadBitmapFont();
glBindTexture(GL_TEXTURE_2D, TEXTURE_BITMAP_FONT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA,
BitmapFont::TEXTURE_DIM, BitmapFont::TEXTURE_DIM,
0, GL_ALPHA, GL_UNSIGNED_BYTE, &BuiltinBitmapFont.texture[0]);
}
int ssglBitmapCharWidth(char32_t codepoint) {
if(codepoint >= 0xe000 && codepoint <= 0xefff) {
// These are special-cased because checkboxes predate support for 2 cell wide
// characters; and so all Printf() calls pad them with spaces.
return 1;
}
LoadBitmapFont();
return BuiltinBitmapFont.GetGlyph(codepoint).advanceCells;
}
double ssglBitmapCharQuad(char32_t codepoint, double x, double y)
{
double s0, t0, s1, t1;
size_t w, h;
if(BuiltinBitmapFont.LocateGlyph(codepoint, &s0, &t0, &s1, &t1, &w, &h)) {
// LocateGlyph modified the texture, reload it.
glEnd();
ssglInitializeBitmapFont();
glBegin(GL_QUADS);
}
if(codepoint >= 0xe000 && codepoint <= 0xefff) {
// Special character, like a checkbox or a radio button
x -= 3;
}
glTexCoord2d(s0, t0);
glVertex2d(x, y - h);
glTexCoord2d(s0, t1);
glVertex2d(x, y);
glTexCoord2d(s1, t1);
glVertex2d(x + w, y);
glTexCoord2d(s1, t0);
glVertex2d(x + w, y - h);
return w;
}
void ssglBitmapText(const std::string &str, Vector p)
{
glEnable(GL_TEXTURE_2D);
glBegin(GL_QUADS);
for(char32_t codepoint : ReadUTF8(str)) {
p.x += ssglBitmapCharQuad(codepoint, p.x, p.y);
}
glEnd();
glDisable(GL_TEXTURE_2D);
}
//-----------------------------------------------------------------------------
// Bitmap font rendering
//-----------------------------------------------------------------------------
static VectorFont BuiltinVectorFont;
static void LoadVectorFont() {
if(!BuiltinVectorFont.IsEmpty()) return;
BuiltinVectorFont = VectorFont::From(LoadStringFromGzip("fonts/unicode.lff.gz"));
}
// Internally and in the UI, the vector font is sized using cap height.
#define FONT_SCALE(h) ((h)/(double)BuiltinVectorFont.capHeight)
double ssglStrCapHeight(double h)
{
return BuiltinVectorFont.capHeight *
FONT_SCALE(h) / SS.GW.scale;
}
double ssglStrFontSize(double h)
{
return (BuiltinVectorFont.ascender - BuiltinVectorFont.descender) *
FONT_SCALE(h) / SS.GW.scale;
}
double ssglStrWidth(const std::string &str, double h)
{
LoadVectorFont();
double width = 0;
for(char32_t codepoint : ReadUTF8(str)) {
width += BuiltinVectorFont.GetGlyph(codepoint).advanceWidth;
}
return width * FONT_SCALE(h) / SS.GW.scale;
}
static Vector PixelAlign(Vector v) {
v = SS.GW.ProjectPoint3(v);
v.x = floor(v.x) + 0.5;
v.y = floor(v.y) + 0.5;
v = SS.GW.UnProjectPoint3(v);
return v;
}
static double DrawCharacter(const VectorFont::Glyph &glyph, Vector t, Vector o, Vector u, Vector v,
double scale, ssglLineFn *fn, void *fndata, bool gridFit) {
double advanceWidth = glyph.advanceWidth;
double actualWidth, offsetX;
if(gridFit) {
o.x += glyph.leftSideBearing;
offsetX = glyph.leftSideBearing;
actualWidth = glyph.boundingWidth;
if(actualWidth == 0) {
// Dot, "i", etc.
actualWidth = 1;
}
} else {
offsetX = 0;
actualWidth = advanceWidth;
}
Vector tt = t;
tt = tt.Plus(u.ScaledBy(o.x * scale));
tt = tt.Plus(v.ScaledBy(o.y * scale));
Vector tu = tt;
tu = tu.Plus(u.ScaledBy(actualWidth * scale));
Vector tv = tt;
tv = tv.Plus(v.ScaledBy(BuiltinVectorFont.capHeight * scale));
if(gridFit) {
tt = PixelAlign(tt);
tu = PixelAlign(tu);
tv = PixelAlign(tv);
}
tu = tu.Minus(tt).ScaledBy(1.0 / actualWidth);
tv = tv.Minus(tt).ScaledBy(1.0 / BuiltinVectorFont.capHeight);
for(const VectorFont::Contour &contour : glyph.contours) {
Vector prevp;
bool penUp = true;
for(const Point2d &pt : contour.points) {
Vector p = tt;
p = p.Plus(tu.ScaledBy(pt.x - offsetX));
p = p.Plus(tv.ScaledBy(pt.y));
if(!penUp) fn(fndata, prevp, p);
prevp = p;
penUp = false;
}
}
return advanceWidth;
}
void ssglWriteText(const std::string &str, double h, Vector t, Vector u, Vector v,
ssglLineFn *fn, void *fndata)
{
LoadVectorFont();
if(!fn) fn = LineDrawCallback;
u = u.WithMagnitude(1);
v = v.WithMagnitude(1);
// Perform grid-fitting only when the text is parallel to the view plane.
bool gridFit = !SS.exportMode && u.Equals(SS.GW.projRight) && v.Equals(SS.GW.projUp);
double scale = FONT_SCALE(h) / SS.GW.scale;
Vector o = {};
for(char32_t codepoint : ReadUTF8(str)) {
o.x += DrawCharacter(BuiltinVectorFont.GetGlyph(codepoint),
t, o, u, v, scale, fn, fndata, gridFit);
}
}
void ssglWriteTextRefCenter(const std::string &str, double h, Vector t, Vector u, Vector v,
ssglLineFn *fn, void *fndata)
{
LoadVectorFont();
u = u.WithMagnitude(1);
v = v.WithMagnitude(1);
double fh = ssglStrCapHeight(h);
double fw = ssglStrWidth(str, h);
t = t.Plus(u.ScaledBy(-fw/2));
t = t.Plus(v.ScaledBy(-fh/2));
ssglWriteText(str, h, t, u, v, fn, fndata);
}
};