577 lines
15 KiB
C++
577 lines
15 KiB
C++
#include "solvespace.h"
|
|
|
|
// A public-domain Hershey vector font ("Simplex").
|
|
#include "font.table"
|
|
|
|
// A bitmap font.
|
|
#include "bitmapfont.table"
|
|
|
|
static bool ColorLocked;
|
|
static bool DepthOffsetLocked;
|
|
|
|
#define FONT_SCALE(h) ((h)/22.0)
|
|
double glxStrWidth(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 glxStrHeight(double h)
|
|
{
|
|
// The characters have height ~22, as they appear in the table.
|
|
return 22.0*FONT_SCALE(h)/SS.GW.scale;
|
|
}
|
|
void glxWriteTextRefCenter(char *str, double h, Vector t, Vector u, Vector v,
|
|
glxLineFn *fn, void *fndata)
|
|
{
|
|
u = u.WithMagnitude(1);
|
|
v = v.WithMagnitude(1);
|
|
|
|
double scale = FONT_SCALE(h)/SS.GW.scale;
|
|
double fh = glxStrHeight(h);
|
|
double fw = glxStrWidth(str, h);
|
|
|
|
t = t.Plus(u.ScaledBy(-fw/2));
|
|
t = t.Plus(v.ScaledBy(-fh/2));
|
|
|
|
// Undo the (+5, +5) offset that glxWriteText applies.
|
|
t = t.Plus(u.ScaledBy(-5*scale));
|
|
t = t.Plus(v.ScaledBy(-5*scale));
|
|
|
|
glxWriteText(str, h, t, u, v, fn, fndata);
|
|
}
|
|
|
|
static void LineDrawCallback(void *fndata, Vector a, Vector b)
|
|
{
|
|
glLineWidth(1);
|
|
glBegin(GL_LINES);
|
|
glxVertex3v(a);
|
|
glxVertex3v(b);
|
|
glEnd();
|
|
}
|
|
|
|
void glxWriteText(char *str, double h, Vector t, Vector u, Vector v,
|
|
glxLineFn *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(prevp.x != VERY_POSITIVE) {
|
|
fn(fndata, prevp, p);
|
|
}
|
|
prevp = p;
|
|
}
|
|
}
|
|
|
|
xo += Font[c].width;
|
|
}
|
|
}
|
|
|
|
void glxVertex3v(Vector u)
|
|
{
|
|
glVertex3f((GLfloat)u.x, (GLfloat)u.y, (GLfloat)u.z);
|
|
}
|
|
|
|
void glxAxisAlignedQuad(double l, double r, double t, double b)
|
|
{
|
|
glBegin(GL_QUADS);
|
|
glVertex2d(l, t);
|
|
glVertex2d(l, b);
|
|
glVertex2d(r, b);
|
|
glVertex2d(r, t);
|
|
glEnd();
|
|
}
|
|
|
|
void glxAxisAlignedLineLoop(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];
|
|
glxVertex3v(p.Plus(u.ScaledBy(c)).Plus(v.ScaledBy(s)));
|
|
}
|
|
glEnd();
|
|
}
|
|
|
|
void glxFatLine(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);
|
|
glxVertex3v(a.Minus(abn));
|
|
glxVertex3v(b.Minus(abn));
|
|
glxVertex3v(b.Plus (abn));
|
|
glxVertex3v(a.Plus (abn));
|
|
glEnd();
|
|
// And the line has two semi-circular end caps.
|
|
FatLineEndcap(a, ab, abn);
|
|
FatLineEndcap(b, ab.ScaledBy(-1), abn);
|
|
}
|
|
|
|
|
|
void glxLockColorTo(DWORD rgb)
|
|
{
|
|
ColorLocked = false;
|
|
glColor3d(REDf(rgb), GREENf(rgb), BLUEf(rgb));
|
|
ColorLocked = true;
|
|
}
|
|
|
|
void glxUnlockColor(void)
|
|
{
|
|
ColorLocked = false;
|
|
}
|
|
|
|
void glxColorRGB(DWORD rgb)
|
|
{
|
|
// Is there a bug in some graphics drivers where this is not equivalent
|
|
// to glColor3d? There seems to be...
|
|
glxColorRGBa(rgb, 1.0);
|
|
}
|
|
|
|
void glxColorRGBa(DWORD rgb, double a)
|
|
{
|
|
if(!ColorLocked) glColor4d(REDf(rgb), GREENf(rgb), BLUEf(rgb), 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, DWORD rgb)
|
|
{
|
|
glEnd();
|
|
glDisable(GL_LIGHTING);
|
|
glxColorRGB(rgb);
|
|
Stipple(s);
|
|
glBegin(GL_TRIANGLES);
|
|
glxVertex3v(tr->a);
|
|
glxVertex3v(tr->b);
|
|
glxVertex3v(tr->c);
|
|
glEnd();
|
|
glEnable(GL_LIGHTING);
|
|
glDisable(GL_POLYGON_STIPPLE);
|
|
glBegin(GL_TRIANGLES);
|
|
}
|
|
|
|
void glxFillMesh(int specColor, SMesh *m, DWORD h, DWORD s1, DWORD s2)
|
|
{
|
|
DWORD rgbHovered = Style::Color(Style::HOVERED),
|
|
rgbSelected = Style::Color(Style::SELECTED);
|
|
|
|
glEnable(GL_NORMALIZE);
|
|
int prevColor = -1;
|
|
glBegin(GL_TRIANGLES);
|
|
for(int i = 0; i < m->l.n; i++) {
|
|
STriangle *tr = &(m->l.elem[i]);
|
|
|
|
int color;
|
|
if(specColor < 0) {
|
|
color = tr->meta.color;
|
|
} else {
|
|
color = specColor;
|
|
}
|
|
if(color != prevColor) {
|
|
GLfloat mpf[] = { REDf(color), GREENf(color), BLUEf(color), 1.0 };
|
|
glEnd();
|
|
glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, mpf);
|
|
prevColor = color;
|
|
glBegin(GL_TRIANGLES);
|
|
}
|
|
|
|
if(0 || 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);
|
|
glxVertex3v(tr->a);
|
|
glxVertex3v(tr->b);
|
|
glxVertex3v(tr->c);
|
|
} else {
|
|
// Use the exact normals that are specified
|
|
glNormal3d((tr->an).x, (tr->an).y, (tr->an).z);
|
|
glxVertex3v(tr->a);
|
|
|
|
glNormal3d((tr->bn).x, (tr->bn).y, (tr->bn).z);
|
|
glxVertex3v(tr->b);
|
|
|
|
glNormal3d((tr->cn).x, (tr->cn).y, (tr->cn).z);
|
|
glxVertex3v(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 GLX_CALLBACK Vertex(Vector *p)
|
|
{
|
|
glxVertex3v(*p);
|
|
}
|
|
void glxFillPolygon(SPolygon *p)
|
|
{
|
|
GLUtesselator *gt = gluNewTess();
|
|
gluTessCallback(gt, GLU_TESS_BEGIN, (glxCallbackFptr *)glBegin);
|
|
gluTessCallback(gt, GLU_TESS_END, (glxCallbackFptr *)glEnd);
|
|
gluTessCallback(gt, GLU_TESS_VERTEX, (glxCallbackFptr *)Vertex);
|
|
|
|
glxTesselatePolygon(gt, p);
|
|
|
|
gluDeleteTess(gt);
|
|
}
|
|
|
|
static void GLX_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 glxTesselatePolygon(GLUtesselator *gt, SPolygon *p)
|
|
{
|
|
int i, j;
|
|
|
|
gluTessCallback(gt, GLU_TESS_COMBINE, (glxCallbackFptr *)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 glxDebugPolygon(SPolygon *p)
|
|
{
|
|
int i, j;
|
|
glLineWidth(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;
|
|
|
|
glxLockColorTo(RGB(0, 0, 255));
|
|
Vector d = (a.Minus(b)).WithMagnitude(-0);
|
|
glBegin(GL_LINES);
|
|
glxVertex3v(a.Plus(d));
|
|
glxVertex3v(b.Minus(d));
|
|
glEnd();
|
|
glxLockColorTo(RGB(255, 0, 0));
|
|
glBegin(GL_POINTS);
|
|
glxVertex3v(a.Plus(d));
|
|
glxVertex3v(b.Minus(d));
|
|
glEnd();
|
|
}
|
|
}
|
|
}
|
|
|
|
void glxDrawEdges(SEdgeList *el, bool endpointsToo)
|
|
{
|
|
SEdge *se;
|
|
glBegin(GL_LINES);
|
|
for(se = el->l.First(); se; se = el->l.NextAfter(se)) {
|
|
glxVertex3v(se->a);
|
|
glxVertex3v(se->b);
|
|
}
|
|
glEnd();
|
|
|
|
if(endpointsToo) {
|
|
glPointSize(12);
|
|
glBegin(GL_POINTS);
|
|
for(se = el->l.First(); se; se = el->l.NextAfter(se)) {
|
|
glxVertex3v(se->a);
|
|
glxVertex3v(se->b);
|
|
}
|
|
glEnd();
|
|
}
|
|
}
|
|
|
|
void glxDebugMesh(SMesh *m)
|
|
{
|
|
int i;
|
|
glLineWidth(1);
|
|
glPointSize(7);
|
|
glxDepthRangeOffset(1);
|
|
glxUnlockColor();
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
|
|
glxColorRGBa(RGB(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;
|
|
|
|
glxVertex3v(t->a);
|
|
glxVertex3v(t->b);
|
|
glxVertex3v(t->c);
|
|
}
|
|
glEnd();
|
|
glxDepthRangeOffset(0);
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
|
}
|
|
|
|
void glxMarkPolygonNormal(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);
|
|
glxVertex3v(tail);
|
|
glxVertex3v(tip);
|
|
glxVertex3v(tip);
|
|
glxVertex3v(tip.Minus(arrow.RotatedAbout(gn, 0.6)));
|
|
glxVertex3v(tip);
|
|
glxVertex3v(tip.Minus(arrow.RotatedAbout(gn, -0.6)));
|
|
glEnd();
|
|
glEnable(GL_LIGHTING);
|
|
}
|
|
|
|
void glxDepthRangeOffset(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 glxDepthRangeLockToFront(bool yes)
|
|
{
|
|
if(yes) {
|
|
DepthOffsetLocked = true;
|
|
glDepthRange(0, 0);
|
|
} else {
|
|
DepthOffsetLocked = false;
|
|
glxDepthRangeOffset(0);
|
|
}
|
|
}
|
|
|
|
void glxCreateBitmapFont(void)
|
|
{
|
|
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,
|
|
16, 128*16,
|
|
0,
|
|
GL_ALPHA, GL_UNSIGNED_BYTE,
|
|
FontTexture);
|
|
}
|
|
|
|
void glxBitmapCharQuad(char c, double x, double y)
|
|
{
|
|
int w = SS.TW.CHAR_WIDTH,
|
|
h = SS.TW.CHAR_HEIGHT;
|
|
|
|
if(c != ' ' && c != 0 && c < 128) {
|
|
double s0 = 0,
|
|
s1 = h/16.0,
|
|
t0 = c/128.0,
|
|
t1 = t0 + (w/16.0)/128;
|
|
|
|
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 glxBitmapText(char *str, Vector p)
|
|
{
|
|
glEnable(GL_TEXTURE_2D);
|
|
glBegin(GL_QUADS);
|
|
while(*str) {
|
|
glxBitmapCharQuad(*str, p.x, p.y);
|
|
|
|
str++;
|
|
p.x += SS.TW.CHAR_WIDTH;
|
|
}
|
|
glEnd();
|
|
glDisable(GL_TEXTURE_2D);
|
|
}
|
|
|
|
void glxDrawPixelsWithTexture(BYTE *data, int w, int h)
|
|
{
|
|
#define MAX_DIM 32
|
|
static BYTE 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);
|
|
}
|
|
|