diff --git a/draw.cpp b/draw.cpp index c699a8f..e5eef8d 100644 --- a/draw.cpp +++ b/draw.cpp @@ -835,14 +835,25 @@ void GraphicsWindow::Paint(int w, int h) { glScaled(scale*2.0/w, scale*2.0/h, scale*1.0/30000); - double tx = projRight.Dot(offset); - double ty = projUp.Dot(offset); - Vector n = projUp.Cross(projRight); - double tz = n.Dot(offset); double mat[16]; - MakeMatrix(mat, projRight.x, projRight.y, projRight.z, tx, - projUp.x, projUp.y, projUp.z, ty, - n.x, n.y, n.z, tz, + // Last thing before display is to apply the perspective + double clp = SS.cameraTangent*scale; + MakeMatrix(mat, 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, clp, 1); + glMultMatrixd(mat); + // Before that, we apply the rotation + Vector n = projUp.Cross(projRight); + MakeMatrix(mat, projRight.x, projRight.y, projRight.z, 0, + projUp.x, projUp.y, projUp.z, 0, + n.x, n.y, n.z, 0, + 0, 0, 0, 1); + glMultMatrixd(mat); + // And before that, the translation + MakeMatrix(mat, 1, 0, 0, offset.x, + 0, 1, 0, offset.y, + 0, 0, 1, offset.z, 0, 0, 0, 1); glMultMatrixd(mat); diff --git a/drawentity.cpp b/drawentity.cpp index 6e5d985..a360719 100644 --- a/drawentity.cpp +++ b/drawentity.cpp @@ -2,15 +2,13 @@ void Entity::LineDrawOrGetDistance(Vector a, Vector b) { if(dogd.drawing) { - // glPolygonOffset works only on polys, not lines, so do it myself - Vector adj = SS.GW.projRight.Cross(SS.GW.projUp); // Draw lines from active group in front of those from previous - int delta = (group.v == SS.GW.activeGroup.v) ? 10 : 5; - adj = adj.ScaledBy(delta/SS.GW.scale); + glxDepthRangeOffset((group.v == SS.GW.activeGroup.v) ? 4 : 2); glBegin(GL_LINES); - glxVertex3v(a.Plus(adj)); - glxVertex3v(b.Plus(adj)); + glxVertex3v(a); + glxVertex3v(b); glEnd(); + glxDepthRangeOffset(0); } else { Point2d ap = SS.GW.ProjectPoint(a); Point2d bp = SS.GW.ProjectPoint(b); @@ -40,7 +38,7 @@ void Entity::DrawAll(void) { Vector r = SS.GW.projRight.ScaledBy(s); Vector d = SS.GW.projUp.ScaledBy(s); glxColor3d(0, 0.8, 0); - glPolygonOffset(-10, -10); + glxDepthRangeOffset(6); glBegin(GL_QUADS); for(i = 0; i < SS.entity.n; i++) { Entity *e = &(SS.entity.elem[i]); @@ -56,7 +54,7 @@ void Entity::DrawAll(void) { glxVertex3v(v.Minus(r).Plus (d)); } glEnd(); - glPolygonOffset(0, 0); + glxDepthRangeOffset(0); } glLineWidth(1.5); @@ -167,14 +165,14 @@ void Entity::DrawOrGetDistance(void) { Vector d = SS.GW.projUp.ScaledBy(s/SS.GW.scale); glxColor3d(0, 0.8, 0); - glPolygonOffset(-10, -10); + glxDepthRangeOffset(6); glBegin(GL_QUADS); glxVertex3v(v.Plus (r).Plus (d)); glxVertex3v(v.Plus (r).Minus(d)); glxVertex3v(v.Minus(r).Minus(d)); glxVertex3v(v.Minus(r).Plus (d)); glEnd(); - glPolygonOffset(0, 0); + glxDepthRangeOffset(0); } else { Point2d pp = SS.GW.ProjectPoint(v); dogd.dmin = pp.DistanceTo(dogd.mp) - 6; @@ -214,10 +212,9 @@ void Entity::DrawOrGetDistance(void) { double s = SS.GW.scale; double h = 60 - SS.GW.height/2; double w = 60 - SS.GW.width/2; - Vector gn = SS.GW.projRight.Cross(SS.GW.projUp); tail = SS.GW.projRight.ScaledBy(w/s).Plus( - SS.GW.projUp. ScaledBy(h/s)).Plus( - gn.ScaledBy(-4*w/s)).Minus(SS.GW.offset); + SS.GW.projUp. ScaledBy(h/s)).Minus(SS.GW.offset); + glxDepthRangeLockToFront(true); glLineWidth(2); } @@ -230,6 +227,7 @@ void Entity::DrawOrGetDistance(void) { LineDrawOrGetDistance(tip,tip.Minus(v.RotatedAbout(axis, 0.6))); LineDrawOrGetDistance(tip,tip.Minus(v.RotatedAbout(axis,-0.6))); } + glxDepthRangeLockToFront(false); glLineWidth(1.5); break; } diff --git a/glhelper.cpp b/glhelper.cpp index cad7e55..9234c4d 100644 --- a/glhelper.cpp +++ b/glhelper.cpp @@ -4,6 +4,7 @@ #include "font.table" static bool ColorLocked; +static bool DepthOffsetLocked; #define FONT_SCALE (0.55) double glxStrWidth(char *str) { @@ -309,6 +310,7 @@ void glxDebugMesh(SMesh *m) int i; glLineWidth(1); glPointSize(7); + glxDepthRangeOffset(1); glxUnlockColor(); for(i = 0; i < m->l.n; i++) { STriangle *t = &(m->l.elem[i]); @@ -321,9 +323,9 @@ void glxDebugMesh(SMesh *m) glxVertex3v(t->b); glxVertex3v(t->c); glEnd(); - glPolygonOffset(0, 0); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } + glxDepthRangeOffset(0); } void glxMarkPolygonNormal(SPolygon *p) @@ -358,3 +360,22 @@ void glxMarkPolygonNormal(SPolygon *p) 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); + } +} + diff --git a/graphicswin.cpp b/graphicswin.cpp index 042c147..98877e6 100644 --- a/graphicswin.cpp +++ b/graphicswin.cpp @@ -125,12 +125,38 @@ void GraphicsWindow::NormalizeProjectionVectors(void) { projRight = projRight.ScaledBy(1/projRight.Magnitude()); } +//----------------------------------------------------------------------------- +// Project a point in model space to screen space, exactly as gl would; return +// units are pixels. +//----------------------------------------------------------------------------- Point2d GraphicsWindow::ProjectPoint(Vector p) { + Vector p3 = ProjectPoint3(p); + Point2d p2 = { p3.x, p3.y }; + return p2; +} +//----------------------------------------------------------------------------- +// Project a point in model space to screen space, exactly as gl would; return +// units are pixels. The z coordinate is also returned, also in pixels. +//----------------------------------------------------------------------------- +Vector GraphicsWindow::ProjectPoint3(Vector p) { + double w; + Vector r = ProjectPoint4(p, &w); + return r.ScaledBy(scale/w); +} +//----------------------------------------------------------------------------- +// Project a point in model space halfway into screen space. The scale is +// not applied, and the perspective divide isn't applied; instead the w +// coordinate is returned separately. +//----------------------------------------------------------------------------- +Vector GraphicsWindow::ProjectPoint4(Vector p, double *w) { p = p.Plus(offset); - Point2d r; - r.x = p.Dot(projRight) * scale; - r.y = p.Dot(projUp) * scale; + Vector r; + r.x = p.Dot(projRight); + r.y = p.Dot(projUp); + r.z = p.Dot(projUp.Cross(projRight)); + + *w = 1 + r.z*SS.cameraTangent*scale; return r; } @@ -180,57 +206,86 @@ void GraphicsWindow::AnimateOntoWorkplane(void) { } void GraphicsWindow::HandlePointForZoomToFit(Vector p, - Point2d *pmax, Point2d *pmin) + Point2d *pmax, Point2d *pmin, double *wmin, bool div) { - Point2d p2 = ProjectPoint(p); - pmax->x = max(pmax->x, p2.x); - pmax->y = max(pmax->y, p2.y); - pmin->x = min(pmin->x, p2.x); - pmin->y = min(pmin->y, p2.y); + double w; + Vector pp = ProjectPoint4(p, &w); + if(div) { + pp = pp.ScaledBy(1.0/w); + } + + pmax->x = max(pmax->x, pp.x); + pmax->y = max(pmax->y, pp.y); + pmin->x = min(pmin->x, pp.x); + pmin->y = min(pmin->y, pp.y); + *wmin = min(*wmin, w); } -void GraphicsWindow::ZoomToFit(void) { +void GraphicsWindow::LoopOverPoints( + Point2d *pmax, Point2d *pmin, double *wmin, bool div) +{ + HandlePointForZoomToFit(Vector::From(0, 0, 0), pmax, pmin, wmin, div); + int i, j; - Point2d pmax = { -1e12, -1e12 }, pmin = { 1e12, 1e12 }; - - HandlePointForZoomToFit(Vector::From(0, 0, 0), &pmax, &pmin); - for(i = 0; i < SS.entity.n; i++) { Entity *e = &(SS.entity.elem[i]); if(!e->IsPoint()) continue; if(!e->IsVisible()) continue; - HandlePointForZoomToFit(e->PointGetNum(), &pmax, &pmin); + HandlePointForZoomToFit(e->PointGetNum(), pmax, pmin, wmin, div); } Group *g = SS.GetGroup(activeGroup); for(i = 0; i < g->mesh.l.n; i++) { STriangle *tr = &(g->mesh.l.elem[i]); - HandlePointForZoomToFit(tr->a, &pmax, &pmin); - HandlePointForZoomToFit(tr->b, &pmax, &pmin); - HandlePointForZoomToFit(tr->c, &pmax, &pmin); + HandlePointForZoomToFit(tr->a, pmax, pmin, wmin, div); + HandlePointForZoomToFit(tr->b, pmax, pmin, wmin, div); + HandlePointForZoomToFit(tr->c, pmax, pmin, wmin, div); } for(i = 0; i < g->poly.l.n; i++) { SContour *sc = &(g->poly.l.elem[i]); for(j = 0; j < sc->l.n; j++) { - HandlePointForZoomToFit(sc->l.elem[j].p, &pmax, &pmin); + HandlePointForZoomToFit(sc->l.elem[j].p, pmax, pmin, wmin, div); } } +} +void GraphicsWindow::ZoomToFit(void) { + // On the first run, ignore perspective. + Point2d pmax = { -1e12, -1e12 }, pmin = { 1e12, 1e12 }; + double wmin = 1; + LoopOverPoints(&pmax, &pmin, &wmin, false); - pmax = pmax.ScaledBy(1/scale); - pmin = pmin.ScaledBy(1/scale); double xm = (pmax.x + pmin.x)/2, ym = (pmax.y + pmin.y)/2; double dx = pmax.x - pmin.x, dy = pmax.y - pmin.y; offset = offset.Plus(projRight.ScaledBy(-xm)).Plus( projUp. ScaledBy(-ym)); - + + // And based on this, we calculate the scale and offset if(dx == 0 && dy == 0) { scale = 5; } else { double scalex = 1e12, scaley = 1e12; if(dx != 0) scalex = 0.9*width /dx; if(dy != 0) scaley = 0.9*height/dy; - scale = min(100, min(scalex, scaley)); + scale = min(scalex, scaley); + + scale = min(100, scale); + scale = max(0.001, scale); + } + + // Then do another run, considering the perspective. + pmax.x = -1e12; pmax.y = -1e12; + pmin.x = 1e12; pmin.y = 1e12; + wmin = 1; + LoopOverPoints(&pmax, &pmin, &wmin, true); + + // Adjust the scale so that no points are behind the camera + if(wmin < 0.1) { + double k = SS.cameraTangent; + // w = 1+k*scale*z + double zmin = (wmin - 1)/(k*scale); + // 0.1 = 1 + k*scale*zmin + // (0.1 - 1)/(k*zmin) = scale + scale = min(scale, (0.1 - 1)/(k*zmin)); } - scale = max(0.001, scale); } void GraphicsWindow::MenuView(int id) { diff --git a/groupmesh.cpp b/groupmesh.cpp index 5128085..e09e384 100644 --- a/groupmesh.cpp +++ b/groupmesh.cpp @@ -314,9 +314,9 @@ void Group::Draw(void) { glEnable(GL_DEPTH_TEST); } else { glxColor4d(0, 0.1, 0.1, 0.5); - glPolygonOffset(-1, -1); + glxDepthRangeOffset(1); glxFillPolygon(&poly); - glPolygonOffset(0, 0); + glxDepthRangeOffset(0); } } diff --git a/mesh.cpp b/mesh.cpp index 0f12c75..1217f0f 100644 --- a/mesh.cpp +++ b/mesh.cpp @@ -258,28 +258,29 @@ bool SMesh::MakeFromInterferenceCheck(SMesh *srca, SMesh *srcb, SMesh *error) { } DWORD SMesh::FirstIntersectionWith(Point2d mp) { - Vector gu = SS.GW.projRight, gv = SS.GW.projUp; - Vector gn = (gu.Cross(gv)).WithMagnitude(1); - - Vector p0 = gu.ScaledBy(mp.x/SS.GW.scale).Plus( - gv.ScaledBy(mp.y/SS.GW.scale)); - p0 = p0.Minus(SS.GW.offset); + Vector p0 = Vector::From(mp.x, mp.y, 0); + Vector gn = Vector::From(0, 0, 1); double maxT = -1e12; DWORD face = 0; int i; for(i = 0; i < l.n; i++) { - STriangle *tr = &(l.elem[i]); - Vector n = tr->Normal(); + STriangle tr = l.elem[i]; + tr.a = SS.GW.ProjectPoint3(tr.a); + tr.b = SS.GW.ProjectPoint3(tr.b); + tr.c = SS.GW.ProjectPoint3(tr.c); + + Vector n = tr.Normal(); + if(n.Dot(gn) < LENGTH_EPS) continue; // back-facing or on edge - if(tr->ContainsPointProjd(gn, p0)) { + if(tr.ContainsPointProjd(gn, p0)) { // Let our line have the form r(t) = p0 + gn*t - double t = (n.Dot((tr->a).Minus(p0)))/(n.Dot(gn)); + double t = -(n.Dot((tr.a).Minus(p0)))/(n.Dot(gn)); if(t > maxT) { maxT = t; - face = tr->meta.face; + face = tr.meta.face; } } } @@ -691,7 +692,7 @@ void SBsp3::DebugDraw(void) { glDisable(GL_LIGHTING); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - glPolygonOffset(-1, 0); + glxDepthRangeOffset(2); glBegin(GL_TRIANGLES); glxVertex3v(tri.a); glxVertex3v(tri.b); @@ -701,14 +702,14 @@ void SBsp3::DebugDraw(void) { glDisable(GL_LIGHTING); glPolygonMode(GL_FRONT_AND_BACK, GL_POINT); glPointSize(10); - glPolygonOffset(-1, 0); + glxDepthRangeOffset(2); glBegin(GL_TRIANGLES); glxVertex3v(tri.a); glxVertex3v(tri.b); glxVertex3v(tri.c); glEnd(); - glPolygonOffset(0, 0); + glxDepthRangeOffset(0); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); more->DebugDraw(); diff --git a/solvespace.cpp b/solvespace.cpp index e8f5af3..9332a3e 100644 --- a/solvespace.cpp +++ b/solvespace.cpp @@ -27,6 +27,8 @@ void SolveSpace::Init(char *cmdLine) { meshTol = ((int)CnfThawDWORD(1000, "MeshTolerance"))/1000.0; // View units viewUnits = (Unit)CnfThawDWORD((DWORD)UNIT_MM, "ViewUnits"); + // Camera tangent (determines perspective) + cameraTangent = ((int)CnfThawDWORD(0, "CameraTangent"))/1e6; // Recent files menus for(i = 0; i < MAX_RECENT; i++) { char name[100]; @@ -77,6 +79,8 @@ void SolveSpace::Exit(void) { CnfFreezeDWORD((int)(meshTol*1000), "MeshTolerance"); // Display/entry units CnfFreezeDWORD((int)viewUnits, "ViewUnits"); + // Camera tangent (determines perspective) + CnfFreezeDWORD((int)(cameraTangent*1e6), "CameraTangent"); ExitNow(); } diff --git a/solvespace.h b/solvespace.h index 46fb9be..e73fcb8 100644 --- a/solvespace.h +++ b/solvespace.h @@ -127,6 +127,8 @@ void glxLockColorTo(double r, double g, double b); void glxUnlockColor(void); void glxColor3d(double r, double g, double b); void glxColor4d(double r, double g, double b, double a); +void glxDepthRangeOffset(int units); +void glxDepthRangeLockToFront(bool yes); #define arraylen(x) (sizeof((x))/sizeof((x)[0])) @@ -253,6 +255,7 @@ public: Vector lightDir[2]; double lightIntensity[2]; double meshTol; + double cameraTangent; int CircleSides(double r); typedef enum { UNIT_MM = 0, diff --git a/textwin.cpp b/textwin.cpp index 1740439..0d69e32 100644 --- a/textwin.cpp +++ b/textwin.cpp @@ -877,6 +877,12 @@ void TextWindow::ScreenChangeMeshTolerance(int link, DWORD v) { ShowTextEditControl(37, 3, str); SS.TW.edit.meaning = EDIT_MESH_TOLERANCE; } +void TextWindow::ScreenChangeCameraTangent(int link, DWORD v) { + char str[1024]; + sprintf(str, "%.3f", 1000*SS.cameraTangent); + ShowTextEditControl(43, 3, str); + SS.TW.edit.meaning = EDIT_CAMERA_TANGENT; +} void TextWindow::ShowConfiguration(void) { int i; Printf(true, "%Ft material color-(r, g, b)"); @@ -908,6 +914,12 @@ void TextWindow::ShowConfiguration(void) { SS.meshTol, &ScreenChangeMeshTolerance, 0, SS.group.elem[SS.group.n-1].mesh.l.n); + + Printf(false, ""); + Printf(false, "%Ft perspective factor (0 for isometric)%E"); + Printf(false, "%Ba %3 %Fl%Ll%f%D[change]%E", + SS.cameraTangent*1000, + &ScreenChangeCameraTangent, 0); } void TextWindow::EditControlDone(char *s) { @@ -974,6 +986,11 @@ void TextWindow::EditControlDone(char *s) { SS.GenerateAll(0, INT_MAX); break; } + case EDIT_CAMERA_TANGENT: { + SS.cameraTangent = (min(2, max(0, atof(s))))/1000.0; + InvalidateGraphics(); + break; + } } SS.later.showTW = true; HideTextEditControl(); diff --git a/ui.h b/ui.h index 7ebbb33..32f5853 100644 --- a/ui.h +++ b/ui.h @@ -63,6 +63,7 @@ public: static const int EDIT_LIGHT_INTENSITY = 4; static const int EDIT_COLOR = 5; static const int EDIT_MESH_TOLERANCE = 6; + static const int EDIT_CAMERA_TANGENT = 7; struct { int meaning; int i; @@ -115,6 +116,7 @@ public: static void ScreenChangeLightIntensity(int link, DWORD v); static void ScreenChangeColor(int link, DWORD v); static void ScreenChangeMeshTolerance(int link, DWORD v); + static void ScreenChangeCameraTangent(int link, DWORD v); void EditControlDone(char *s); }; @@ -217,9 +219,13 @@ public: void NormalizeProjectionVectors(void); Point2d ProjectPoint(Vector p); + Vector ProjectPoint3(Vector p); + Vector ProjectPoint4(Vector p, double *w); void AnimateOntoWorkplane(void); Vector VectorFromProjs(Vector rightUpForward); - void HandlePointForZoomToFit(Vector p, Point2d *pmax, Point2d *pmin); + void HandlePointForZoomToFit(Vector p, Point2d *pmax, Point2d *pmin, + double *wmin, bool div); + void LoopOverPoints(Point2d *pmax, Point2d *pmin, double *wmin, bool div); void ZoomToFit(void); hGroup activeGroup;