
Now it is possible to give non-ASCII names to groups as well as see non-ASCII filenames of imported files. In the future this makes localization possible. This works for LTR languages, such as European and CJK, but not RTL such as Arabic. Does Arabic even exist in monospaced form? I have no idea.
702 lines
19 KiB
C++
702 lines
19 KiB
C++
//-----------------------------------------------------------------------------
|
|
// Helper functions that ultimately draw stuff with gl.
|
|
//
|
|
// Copyright 2008-2013 Jonathan Westhues.
|
|
//-----------------------------------------------------------------------------
|
|
#include <zlib.h>
|
|
#include "solvespace.h"
|
|
|
|
namespace SolveSpace {
|
|
|
|
// A public-domain Hershey vector font ("Simplex").
|
|
#include "font.table.h"
|
|
|
|
// A bitmap font.
|
|
#include "generated/bitmapfont.table.h"
|
|
|
|
static bool ColorLocked;
|
|
static bool DepthOffsetLocked;
|
|
|
|
#define FONT_SCALE(h) ((h)/22.0)
|
|
double ssglStrWidth(const char *str, double h)
|
|
{
|
|
int w = 0;
|
|
for(; *str; str++) {
|
|
int c = *str;
|
|
if(c < 32 || c > 126) c = 32;
|
|
c -= 32;
|
|
|
|
w += Font[c].width;
|
|
}
|
|
return w*FONT_SCALE(h)/SS.GW.scale;
|
|
}
|
|
double ssglStrHeight(double h)
|
|
{
|
|
// The characters have height ~22, as they appear in the table.
|
|
return 22.0*FONT_SCALE(h)/SS.GW.scale;
|
|
}
|
|
void ssglWriteTextRefCenter(const char *str, double h, Vector t, Vector u, Vector v,
|
|
ssglLineFn *fn, void *fndata)
|
|
{
|
|
u = u.WithMagnitude(1);
|
|
v = v.WithMagnitude(1);
|
|
|
|
double scale = FONT_SCALE(h)/SS.GW.scale;
|
|
double fh = ssglStrHeight(h);
|
|
double fw = ssglStrWidth(str, h);
|
|
|
|
t = t.Plus(u.ScaledBy(-fw/2));
|
|
t = t.Plus(v.ScaledBy(-fh/2));
|
|
|
|
// Undo the (+5, +5) offset that ssglWriteText applies.
|
|
t = t.Plus(u.ScaledBy(-5*scale));
|
|
t = t.Plus(v.ScaledBy(-5*scale));
|
|
|
|
ssglWriteText(str, h, t, u, v, fn, fndata);
|
|
}
|
|
|
|
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.6)
|
|
width = 1.6;
|
|
|
|
glLineWidth(width);
|
|
}
|
|
|
|
static void LineDrawCallback(void *fndata, Vector a, Vector b)
|
|
{
|
|
ssglLineWidth(1);
|
|
glBegin(GL_LINES);
|
|
ssglVertex3v(a);
|
|
ssglVertex3v(b);
|
|
glEnd();
|
|
}
|
|
|
|
void ssglWriteText(const char *str, double h, Vector t, Vector u, Vector v,
|
|
ssglLineFn *fn, void *fndata)
|
|
{
|
|
if(!fn) fn = LineDrawCallback;
|
|
u = u.WithMagnitude(1);
|
|
v = v.WithMagnitude(1);
|
|
|
|
double scale = FONT_SCALE(h)/SS.GW.scale;
|
|
int xo = 5;
|
|
int yo = 5;
|
|
|
|
for(; *str; str++) {
|
|
int c = *str;
|
|
if(c < 32 || c > 126) c = 32;
|
|
|
|
c -= 32;
|
|
|
|
int j;
|
|
Vector prevp = Vector::From(VERY_POSITIVE, 0, 0);
|
|
for(j = 0; j < Font[c].points; j++) {
|
|
int x = Font[c].coord[j*2];
|
|
int y = Font[c].coord[j*2+1];
|
|
|
|
if(x == PEN_UP && y == PEN_UP) {
|
|
prevp.x = VERY_POSITIVE;
|
|
} else {
|
|
Vector p = t;
|
|
p = p.Plus(u.ScaledBy((xo + x)*scale));
|
|
p = p.Plus(v.ScaledBy((yo + y)*scale));
|
|
if(EXACT(prevp.x != VERY_POSITIVE)) {
|
|
fn(fndata, prevp, p);
|
|
}
|
|
prevp = p;
|
|
}
|
|
}
|
|
|
|
xo += Font[c].width;
|
|
}
|
|
}
|
|
|
|
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 ssglFatLine(Vector a, Vector b, double width)
|
|
{
|
|
// 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)
|
|
{
|
|
SEdge *se;
|
|
glBegin(GL_LINES);
|
|
for(se = el->l.First(); se; se = el->l.NextAfter(se)) {
|
|
ssglVertex3v(se->a);
|
|
ssglVertex3v(se->b);
|
|
}
|
|
glEnd();
|
|
|
|
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 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);
|
|
}
|
|
}
|
|
|
|
const int BitmapFontChunkSize = 64 * 64;
|
|
static bool BitmapFontChunkInitialized[0x10000 / BitmapFontChunkSize];
|
|
static int BitmapFontCurrentChunk = -1;
|
|
|
|
static void CreateBitmapFontChunk(const uint8_t *source, size_t sourceLength,
|
|
int textureIndex)
|
|
{
|
|
// Place the font in our texture in a two-dimensional grid.
|
|
// The maximum texture size that is reasonably supported is 1024x1024.
|
|
const size_t fontTextureSize = BitmapFontChunkSize*16*16;
|
|
uint8_t *fontTexture = (uint8_t *)malloc(fontTextureSize),
|
|
*mappedTexture = (uint8_t *)malloc(fontTextureSize);
|
|
|
|
z_stream stream;
|
|
stream.zalloc = Z_NULL;
|
|
stream.zfree = Z_NULL;
|
|
stream.opaque = Z_NULL;
|
|
if(inflateInit(&stream) != Z_OK)
|
|
oops();
|
|
|
|
stream.next_in = (Bytef *)source;
|
|
stream.avail_in = sourceLength;
|
|
stream.next_out = fontTexture;
|
|
stream.avail_out = fontTextureSize;
|
|
if(inflate(&stream, Z_NO_FLUSH) != Z_STREAM_END)
|
|
oops();
|
|
if(stream.avail_out != 0)
|
|
oops();
|
|
|
|
inflateEnd(&stream);
|
|
|
|
for(int a = 0; a < BitmapFontChunkSize; a++) {
|
|
int row = a / 64, col = a % 64;
|
|
|
|
for(int i = 0; i < 16; i++) {
|
|
memcpy(mappedTexture + row*64*16*16 + col*16 + i*64*16,
|
|
fontTexture + a*16*16 + i*16,
|
|
16);
|
|
}
|
|
}
|
|
|
|
free(fontTexture);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, textureIndex);
|
|
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,
|
|
16*64, 64*16,
|
|
0,
|
|
GL_ALPHA, GL_UNSIGNED_BYTE,
|
|
mappedTexture);
|
|
|
|
free(mappedTexture);
|
|
}
|
|
|
|
static void SwitchToBitmapFontChunkFor(char32_t chr)
|
|
{
|
|
int plane = chr / BitmapFontChunkSize,
|
|
textureIndex = TEXTURE_BITMAP_FONT + plane;
|
|
|
|
if(BitmapFontCurrentChunk != textureIndex) {
|
|
glEnd();
|
|
|
|
if(!BitmapFontChunkInitialized[plane]) {
|
|
CreateBitmapFontChunk(CompressedFontTexture[plane].data,
|
|
CompressedFontTexture[plane].length,
|
|
textureIndex);
|
|
BitmapFontChunkInitialized[plane] = true;
|
|
} else {
|
|
glBindTexture(GL_TEXTURE_2D, textureIndex);
|
|
}
|
|
|
|
BitmapFontCurrentChunk = textureIndex;
|
|
|
|
glBegin(GL_QUADS);
|
|
}
|
|
}
|
|
|
|
void ssglInitializeBitmapFont()
|
|
{
|
|
memset(BitmapFontChunkInitialized, 0, sizeof(BitmapFontChunkInitialized));
|
|
BitmapFontCurrentChunk = -1;
|
|
}
|
|
|
|
int ssglBitmapCharWidth(char32_t chr) {
|
|
if(!CodepointProperties[chr].exists)
|
|
oops();
|
|
return CodepointProperties[chr].isWide ? 2 : 1;
|
|
}
|
|
|
|
void ssglBitmapCharQuad(char32_t chr, double x, double y)
|
|
{
|
|
int w, h;
|
|
|
|
h = 16;
|
|
if(chr >= 0xe000 && chr <= 0xefff) {
|
|
// Special character, like a checkbox or a radio button
|
|
w = 16;
|
|
x -= 3;
|
|
} else if(CodepointProperties[chr].isWide) {
|
|
// Wide (usually CJK or reserved) character
|
|
w = 16;
|
|
} else {
|
|
// Normal character
|
|
w = 8;
|
|
}
|
|
|
|
if(chr != ' ' && chr != 0) {
|
|
int n = chr % BitmapFontChunkSize;
|
|
int row = n / 64, col = n % 64;
|
|
double s0 = col/64.0,
|
|
s1 = (col+1)/64.0,
|
|
t0 = row/64.0,
|
|
t1 = t0 + (w/16.0)/64;
|
|
|
|
SwitchToBitmapFontChunkFor(chr);
|
|
|
|
glTexCoord2d(s1, t0);
|
|
glVertex2d(x, y);
|
|
|
|
glTexCoord2d(s1, t1);
|
|
glVertex2d(x + w, y);
|
|
|
|
glTexCoord2d(s0, t1);
|
|
glVertex2d(x + w, y - h);
|
|
|
|
glTexCoord2d(s0, t0);
|
|
glVertex2d(x, y - h);
|
|
}
|
|
}
|
|
|
|
void ssglBitmapText(const char *str, Vector p)
|
|
{
|
|
glEnable(GL_TEXTURE_2D);
|
|
glBegin(GL_QUADS);
|
|
while(*str) {
|
|
char32_t chr;
|
|
str = ReadUTF8(str, &chr);
|
|
|
|
ssglBitmapCharQuad(chr, p.x, p.y);
|
|
p.x += 8 * ssglBitmapCharWidth(chr);
|
|
}
|
|
glEnd();
|
|
glDisable(GL_TEXTURE_2D);
|
|
}
|
|
|
|
void ssglDrawPixelsWithTexture(uint8_t *data, int w, int h)
|
|
{
|
|
#define MAX_DIM 32
|
|
static uint8_t Texture[MAX_DIM*MAX_DIM*3];
|
|
int i, j;
|
|
if(w > MAX_DIM || h > MAX_DIM) oops();
|
|
|
|
for(i = 0; i < w; i++) {
|
|
for(j = 0; j < h; j++) {
|
|
Texture[(j*MAX_DIM + i)*3 + 0] = data[(j*w + i)*3 + 0];
|
|
Texture[(j*MAX_DIM + i)*3 + 1] = data[(j*w + i)*3 + 1];
|
|
Texture[(j*MAX_DIM + i)*3 + 2] = data[(j*w + i)*3 + 2];
|
|
}
|
|
}
|
|
|
|
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_DECAL);
|
|
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, MAX_DIM, MAX_DIM, 0,
|
|
GL_RGB, GL_UNSIGNED_BYTE, Texture);
|
|
|
|
glEnable(GL_TEXTURE_2D);
|
|
glBegin(GL_QUADS);
|
|
glTexCoord2d(0, 0);
|
|
glVertex2d(0, h);
|
|
|
|
glTexCoord2d(((double)w)/MAX_DIM, 0);
|
|
glVertex2d(w, h);
|
|
|
|
glTexCoord2d(((double)w)/MAX_DIM, ((double)h)/MAX_DIM);
|
|
glVertex2d(w, 0);
|
|
|
|
glTexCoord2d(0, ((double)h)/MAX_DIM);
|
|
glVertex2d(0, 0);
|
|
glEnd();
|
|
glDisable(GL_TEXTURE_2D);
|
|
}
|
|
|
|
};
|