
This is a high-SNR warning that's enabled by default on MSVC and it has highlighted some bugs in glhelper.cpp (that are also fixed in this commit). Unfortunately GCC does not have an equivalent for that warning, and -Wconversion is very noisy.
822 lines
24 KiB
C++
822 lines
24 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: oops();
|
|
}
|
|
ssglStippledLine(a, b, width, stipplePattern, stippleScale, maybeFat);
|
|
}
|
|
|
|
void ssglStippledLine(Vector a, Vector b, double width,
|
|
const char *stipplePattern, double stippleScale, bool maybeFat)
|
|
{
|
|
if(stipplePattern == NULL || *stipplePattern == 0) oops();
|
|
|
|
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: oops();
|
|
}
|
|
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(void)
|
|
{
|
|
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));
|
|
|
|
sol->FillOutlineTags(projDir);
|
|
for(SOutline *so = sol->l.First(); so; so = sol->l.NextAfter(so)) {
|
|
if(!so->tag) 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);
|
|
}
|
|
|
|
};
|