From e7c8c1c8f2084bc1aa26460b123f34e203542c84 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 31 May 2016 00:55:13 +0000 Subject: [PATCH] Abstract all (ex-OpenGL) drawing operations into a Canvas interface. This has several desirable consequences: * It is now possible to port SolveSpace to a later version of OpenGL, such as OpenGLES 2, so that it runs on platforms that only have that OpenGL version; * The majority of geometry is now rendered without references to the camera in C++ code, so a renderer can now submit it to the video card once and re-rasterize with a different projection matrix every time the projection is changed, avoiding expensive reuploads; * The DOGD (draw or get distance) interface is now a straightforward Canvas implementation; * There are no more direct references to SS.GW.(projection) in sketch rendering code, which allows rendering to multiple viewports; * There are no more unnecessary framebuffer flips on CPU on Cocoa and GTK; * The platform-dependent GL code is now confined to rendergl1.cpp. * The Microsoft and Apple headers required by it that are prone to identifier conflicts are no longer included globally; * The rendergl1.cpp implementation can now be omitted from compilation to run SolveSpace headless or with a different OpenGL version. Note these implementation details of Canvas: * GetCamera currently always returns a reference to the field `Camera camera;`. This is so that a future renderer that caches geometry in the video memory can define it as asserting, which would provide assurance against code that could accidentally put something projection-dependent in the cache; * Line and triangle rendering is specified through a level of indirection, hStroke and hFill. This is so that a future renderer that batches geometry could cheaply group identical styles. * DrawPixmap and DrawVectorText accept a (o,u,v) and not a matrix. This is so that a future renderer into an output format that uses 2d transforms (e.g. SVG) could easily derive those. Some additional internal changes were required to enable this: * Pixmap is now always passed as std::shared_ptr<{const ,}Pixmap>. This is so that the renderer could cache uploaded textures between API calls, which requires it to capture a (weak) reference. * The PlatformPathEqual function was properly extracted into platform-specific code. This is so that the header could be included only where needed (in platform/w32* as well as rendergl1.cpp). * The SBsp{2,3}::DebugDraw functions were removed. They can be rewritten using the Canvas API if they are ever needed. While no visual changes were originally intended, some minor fixes happened anyway: * The "emphasis" yellow line from top-left corner is now correctly rendered much wider. * The marquee rectangle is now pixel grid aligned. * The hidden entities now do not clobber the depth buffer, removing some minor artifacts. * The workplane "tab" now scales with the font used to render the workplane name. * The workplane name font is now taken from the normals style. * Workplane and constraint line stipple is insignificantly different. This is so that it can reuse the existing stipple codepaths; rendering of workplanes and constraints predates those. Some debug functionality was added: * In graphics window, an fps counter that becomes red when rendering under 60fps is drawn. --- src/CMakeLists.txt | 4 +- src/bsp.cpp | 58 --- src/confscreen.cpp | 8 +- src/draw.cpp | 649 +++++++++++++-------------- src/drawconstraint.cpp | 621 ++++++++++++-------------- src/drawentity.cpp | 400 +++++++---------- src/dsc.h | 9 + src/export.cpp | 122 +++-- src/file.cpp | 17 +- src/glhelper.cpp | 835 ----------------------------------- src/graphicswin.cpp | 6 +- src/groupmesh.cpp | 259 ++++++----- src/importdxf.cpp | 5 - src/mouse.cpp | 7 +- src/platform/cocoamain.mm | 11 +- src/platform/gloffscreen.cpp | 22 +- src/platform/gloffscreen.h | 4 +- src/platform/gtkmain.cpp | 18 +- src/platform/unixutil.cpp | 14 + src/platform/w32main.cpp | 17 +- src/platform/w32util.cpp | 13 + src/polygon.h | 6 +- src/render/render.cpp | 353 +++++++++++++++ src/render/render.h | 254 +++++++++++ src/render/rendergl1.cpp | 720 ++++++++++++++++++++++++++++++ src/resource.cpp | 323 +++++++++++--- src/resource.h | 42 +- src/sketch.h | 119 +++-- src/solvespace.h | 69 +-- src/srf/surface.h | 4 - src/style.cpp | 59 +-- src/textwin.cpp | 324 +++++--------- src/toolbar.cpp | 83 ++-- src/ui.h | 34 +- src/undoredo.cpp | 1 - src/util.cpp | 17 + 36 files changed, 2941 insertions(+), 2566 deletions(-) delete mode 100644 src/glhelper.cpp create mode 100644 src/render/render.cpp create mode 100644 src/render/render.h create mode 100644 src/render/rendergl1.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4de85d11..9e794e22 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -137,6 +137,7 @@ set(solvespace_HEADERS sketch.h solvespace.h ui.h + render/render.h srf/surface.h) set(solvespace_SOURCES @@ -156,7 +157,6 @@ set(solvespace_SOURCES expr.cpp file.cpp generate.cpp - glhelper.cpp graphicswin.cpp group.cpp groupmesh.cpp @@ -177,6 +177,8 @@ set(solvespace_SOURCES undoredo.cpp util.cpp view.cpp + render/render.cpp + render/rendergl1.cpp srf/boolean.cpp srf/curve.cpp srf/merge.cpp diff --git a/src/bsp.cpp b/src/bsp.cpp index 283e9a38..06bca1bc 100644 --- a/src/bsp.cpp +++ b/src/bsp.cpp @@ -423,48 +423,6 @@ void SBsp3::GenerateInPaintOrder(SMesh *m) const { } } -void SBsp3::DebugDraw() const { - - if(pos) pos->DebugDraw(); - Vector norm = tri.Normal(); - glNormal3d(norm.x, norm.y, norm.z); - - glEnable(GL_DEPTH_TEST); - glEnable(GL_LIGHTING); - glBegin(GL_TRIANGLES); - ssglVertex3v(tri.a); - ssglVertex3v(tri.b); - ssglVertex3v(tri.c); - glEnd(); - - glDisable(GL_LIGHTING); - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - ssglDepthRangeOffset(2); - glBegin(GL_TRIANGLES); - ssglVertex3v(tri.a); - ssglVertex3v(tri.b); - ssglVertex3v(tri.c); - glEnd(); - - glDisable(GL_LIGHTING); - glPolygonMode(GL_FRONT_AND_BACK, GL_POINT); - glPointSize(10); - ssglDepthRangeOffset(2); - glBegin(GL_TRIANGLES); - ssglVertex3v(tri.a); - ssglVertex3v(tri.b); - ssglVertex3v(tri.c); - glEnd(); - - ssglDepthRangeOffset(0); - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - - if(more) more->DebugDraw(); - if(neg) neg->DebugDraw(); - - if(edges) edges->DebugDraw(n, d); -} - ///////////////////////////////// Vector SBsp2::IntersectionWith(Vector a, Vector b) const { @@ -663,19 +621,3 @@ void SBsp2::InsertTriangle(STriangle *tr, SMesh *m, SBsp3 *bsp3) { return; } - -void SBsp2::DebugDraw(Vector n, double d) const { - ssassert(fabs((edge.a).Dot(n) - d) < LENGTH_EPS, "Endpoint too close to BSP node plane"); - ssassert(fabs((edge.b).Dot(n) - d) < LENGTH_EPS, "Endpoint too close to BSP node plane"); - - ssglLineWidth(10); - glBegin(GL_LINES); - ssglVertex3v(edge.a); - ssglVertex3v(edge.b); - glEnd(); - if(pos) pos->DebugDraw(n, d); - if(neg) neg->DebugDraw(n, d); - if(more) more->DebugDraw(n, d); - ssglLineWidth(1); -} - diff --git a/src/confscreen.cpp b/src/confscreen.cpp index c972de1f..3b66a6aa 100644 --- a/src/confscreen.cpp +++ b/src/confscreen.cpp @@ -302,10 +302,12 @@ void TextWindow::ShowConfiguration() { Printf(false, "%Ba %d %Fl%Ll%f[change]%E", SS.autosaveInterval, &ScreenChangeAutosaveInterval); + const char *gl_vendor, *gl_renderer, *gl_version; + OpenGl1Renderer::GetIdent(&gl_vendor, &gl_renderer, &gl_version); Printf(false, ""); - Printf(false, " %Ftgl vendor %E%s", glGetString(GL_VENDOR)); - Printf(false, " %Ft renderer %E%s", glGetString(GL_RENDERER)); - Printf(false, " %Ft version %E%s", glGetString(GL_VERSION)); + Printf(false, " %Ftgl vendor %E%s", gl_vendor); + Printf(false, " %Ft renderer %E%s", gl_renderer); + Printf(false, " %Ft version %E%s", gl_version); } bool TextWindow::EditControlDoneForConfiguration(const char *s) { diff --git a/src/draw.cpp b/src/draw.cpp index 480e9ec6..d62327d4 100644 --- a/src/draw.cpp +++ b/src/draw.cpp @@ -30,40 +30,49 @@ void GraphicsWindow::Selection::Clear() { emphasized = false; } -void GraphicsWindow::Selection::Draw() { - Vector refp[2]; - refp[0] = refp[1] = Vector::From(0, 0, 0); +void GraphicsWindow::Selection::Draw(bool isHovered, Canvas *canvas) { + const Camera &camera = canvas->GetCamera(); + + std::vector refs; if(entity.v) { Entity *e = SK.GetEntity(entity); - e->Draw(/*drawAsHidden=*/false); + e->Draw(isHovered ? Entity::DrawAs::HOVERED : + Entity::DrawAs::SELECTED, + canvas); if(emphasized) { - refp[0] = refp[1] = e->GetReferencePos(); + e->GetReferencePoints(&refs); } } if(constraint.v) { Constraint *c = SK.GetConstraint(constraint); - c->Draw(); - if(emphasized) c->GetReferencePos(refp); + c->Draw(isHovered ? Constraint::DrawAs::HOVERED : + Constraint::DrawAs::SELECTED, + canvas); + if(emphasized) { + c->GetReferencePoints(camera, &refs); + } } if(emphasized && (constraint.v || entity.v)) { // We want to emphasize this constraint or entity, by drawing a thick // line from the top left corner of the screen to the reference point(s) // of that entity or constraint. - double s = 0.501/SS.GW.scale; - Vector topLeft = SS.GW.projRight.ScaledBy(-SS.GW.width*s); - topLeft = topLeft.Plus(SS.GW.projUp.ScaledBy(SS.GW.height*s)); - topLeft = topLeft.Minus(SS.GW.offset); + Canvas::Stroke strokeEmphasis = {}; + strokeEmphasis.layer = Canvas::Layer::FRONT; + strokeEmphasis.color = Style::Color(Style::HOVERED).WithAlpha(50); + strokeEmphasis.width = 40; + Canvas::hStroke hcsEmphasis = canvas->GetStroke(strokeEmphasis); - ssglLineWidth(40); - RgbaColor rgb = Style::Color(Style::HOVERED); - glColor4d(rgb.redF(), rgb.greenF(), rgb.blueF(), 0.2); - for(int i = 0; i < (refp[0].Equals(refp[1]) ? 1 : 2); i++) { - glBegin(GL_LINES); - ssglVertex3v(topLeft); - ssglVertex3v(refp[i]); - glEnd(); + Point2d topLeftScreen; + topLeftScreen.x = -(double)camera.width / 2; + topLeftScreen.y = (double)camera.height / 2; + Vector topLeft = camera.UnProjectPoint(topLeftScreen); + + auto it = std::unique(refs.begin(), refs.end(), + [](Vector a, Vector b) { return a.Equals(b); }); + refs.erase(it, refs.end()); + for(Vector p : refs) { + canvas->DrawLine(topLeft, p, hcsEmphasis); } - ssglLineWidth(1); } } @@ -285,9 +294,31 @@ void GraphicsWindow::GroupSelection() { } } +Camera GraphicsWindow::GetCamera() const { + Camera camera = {}; + camera.width = (int)width; + camera.height = (int)height; + camera.offset = offset; + camera.projUp = projUp; + camera.projRight = projRight; + camera.scale = scale; + camera.tangent = SS.CameraTangent(); + camera.hasPixels = true; + return camera; +} + +Lighting GraphicsWindow::GetLighting() const { + Lighting lighting = {}; + lighting.backgroundColor = SS.backgroundColor; + lighting.ambientIntensity = SS.ambientIntensity; + lighting.lightIntensity[0] = SS.lightIntensity[0]; + lighting.lightIntensity[1] = SS.lightIntensity[1]; + lighting.lightDirection[0] = SS.lightDir[0]; + lighting.lightDirection[1] = SS.lightDir[1]; + return lighting; +} + void GraphicsWindow::HitTestMakeSelection(Point2d mp) { - int i; - double d, dmin = 1e12; Selection s = {}; // Did the view projection change? If so, invalidate bounding boxes. @@ -304,40 +335,45 @@ void GraphicsWindow::HitTestMakeSelection(Point2d mp) { } } + ObjectPicker canvas = {}; + canvas.camera = GetCamera(); + canvas.selRadius = 10.0; + canvas.point = mp; + canvas.maxZIndex = -1; + // Always do the entities; we might be dragging something that should // be auto-constrained, and we need the hover for that. - for(i = 0; i < SK.entity.n; i++) { - Entity *e = &(SK.entity.elem[i]); + for(Entity &e : SK.entity) { + if(!e.IsVisible()) continue; + // Don't hover whatever's being dragged. - if(e->h.request().v == pending.point.request().v) { + if(e.h.request().v == pending.point.request().v) { // The one exception is when we're creating a new cubic; we // want to be able to hover the first point, because that's // how we turn it into a periodic spline. - if(!e->IsPoint()) continue; - if(!e->h.isFromRequest()) continue; - Request *r = SK.GetRequest(e->h.request()); + if(!e.IsPoint()) continue; + if(!e.h.isFromRequest()) continue; + Request *r = SK.GetRequest(e.h.request()); if(r->type != Request::Type::CUBIC) continue; if(r->extraPoints < 2) continue; - if(e->h.v != r->h.entity(1).v) continue; + if(e.h.v != r->h.entity(1).v) continue; } - d = e->GetDistance(mp); - if(d < SELECTION_RADIUS && d < dmin) { + if(canvas.Pick([&]{ e.Draw(Entity::DrawAs::DEFAULT, &canvas); })) { s = {}; - s.entity = e->h; - dmin = d; + s.entity = e.h; } } // The constraints and faces happen only when nothing's in progress. if(pending.operation == Pending::NONE) { // Constraints - for(i = 0; i < SK.constraint.n; i++) { - d = SK.constraint.elem[i].GetDistance(mp); - if(d < SELECTION_RADIUS && d < dmin) { + for(Constraint &c : SK.constraint) { + if(!c.IsVisible()) continue; + + if(canvas.Pick([&]{ c.Draw(Constraint::DrawAs::DEFAULT, &canvas); })) { s = {}; - s.constraint = SK.constraint.elem[i].h; - dmin = d; + s.constraint = c.h; } } @@ -440,107 +476,128 @@ void GraphicsWindow::NormalizeProjectionVectors() { projRight = projRight.WithMagnitude(1); } -Vector GraphicsWindow::VectorFromProjs(Vector rightUpForward) { - Vector n = projRight.Cross(projUp); +void GraphicsWindow::DrawSnapGrid(Canvas *canvas) { + if(!LockedInWorkplane()) return; - Vector r = (projRight.ScaledBy(rightUpForward.x)); - r = r.Plus(projUp.ScaledBy(rightUpForward.y)); - r = r.Plus(n.ScaledBy(rightUpForward.z)); - return r; + hEntity he = ActiveWorkplane(); + EntityBase *wrkpl = SK.GetEntity(he), + *norm = wrkpl->Normal(); + Vector n = projUp.Cross(projRight); + Vector wu, wv, wn, wp; + wp = SK.GetEntity(wrkpl->point[0])->PointGetNum(); + wu = norm->NormalU(); + wv = norm->NormalV(); + wn = norm->NormalN(); + + double g = SS.gridSpacing; + + double umin = VERY_POSITIVE, umax = VERY_NEGATIVE, + vmin = VERY_POSITIVE, vmax = VERY_NEGATIVE; + int a; + for(a = 0; a < 4; a++) { + // Ideally, we would just do +/- half the width and height; but + // allow some extra slop for rounding. + Vector horiz = projRight.ScaledBy((0.6*width)/scale + 2*g), + vert = projUp. ScaledBy((0.6*height)/scale + 2*g); + if(a == 2 || a == 3) horiz = horiz.ScaledBy(-1); + if(a == 1 || a == 3) vert = vert. ScaledBy(-1); + Vector tp = horiz.Plus(vert).Minus(offset); + + // Project the point into our grid plane, normal to the screen + // (not to the grid plane). If the plane is on edge then this is + // impossible so don't try to draw the grid. + bool parallel; + Vector tpp = Vector::AtIntersectionOfPlaneAndLine( + wn, wn.Dot(wp), + tp, tp.Plus(n), + ¶llel); + if(parallel) return; + + tpp = tpp.Minus(wp); + double uu = tpp.Dot(wu), + vv = tpp.Dot(wv); + + umin = min(uu, umin); + umax = max(uu, umax); + vmin = min(vv, vmin); + vmax = max(vv, vmax); + } + + int i, j, i0, i1, j0, j1; + + i0 = (int)(umin / g); + i1 = (int)(umax / g); + j0 = (int)(vmin / g); + j1 = (int)(vmax / g); + + if(i0 > i1 || i1 - i0 > 400) return; + if(j0 > j1 || j1 - j0 > 400) return; + + Canvas::Stroke stroke = {}; + stroke.layer = Canvas::Layer::BACK; + stroke.color = Style::Color(Style::DATUM).WithAlpha(75); + Canvas::hStroke hcs = canvas->GetStroke(stroke); + + for(i = i0 + 1; i < i1; i++) { + canvas->DrawLine(wp.Plus(wu.ScaledBy(i*g)).Plus(wv.ScaledBy(j0*g)), + wp.Plus(wu.ScaledBy(i*g)).Plus(wv.ScaledBy(j1*g)), + hcs); + } + for(j = j0 + 1; j < j1; j++) { + canvas->DrawLine(wp.Plus(wu.ScaledBy(i0*g)).Plus(wv.ScaledBy(j*g)), + wp.Plus(wu.ScaledBy(i1*g)).Plus(wv.ScaledBy(j*g)), + hcs); + } } -void GraphicsWindow::Paint() { - int i; - havePainted = true; +void GraphicsWindow::DrawPersistent(Canvas *canvas) { + // Draw the active group; this does stuff like the mesh and edges. + SK.GetGroup(activeGroup)->Draw(canvas); - int w, h; - GetGraphicsWindowSize(&w, &h); - width = w; height = h; - glViewport(0, 0, w, h); - - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - - glScaled(scale*2.0/w, scale*2.0/h, scale*1.0/30000); - - double mat[16]; - // 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); - - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - - glShadeModel(GL_SMOOTH); - - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glEnable(GL_BLEND); - glEnable(GL_LINE_SMOOTH); - // don't enable GL_POLYGON_SMOOTH; that looks ugly on some graphics cards, - // drawn with leaks in the mesh - glEnable(GL_POLYGON_OFFSET_LINE); - glEnable(GL_POLYGON_OFFSET_FILL); - glEnable(GL_DEPTH_TEST); - glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); - glEnable(GL_NORMALIZE); - - // At the same depth, we want later lines drawn over earlier. - glDepthFunc(GL_LEQUAL); - - if(SS.ActiveGroupsOkay()) { - glClearColor(SS.backgroundColor.redF(), - SS.backgroundColor.greenF(), - SS.backgroundColor.blueF(), 1.0f); - } else { - // Draw a different background whenever we're having solve problems. - RgbaColor rgb = Style::Color(Style::DRAW_ERROR); - glClearColor(0.4f*rgb.redF(), 0.4f*rgb.greenF(), 0.4f*rgb.blueF(), 1.0f); - // And show the text window, which has info to debug it - ForceTextWindowShown(); + // Now draw the entities. + for(Entity &e : SK.entity) { + if(SS.GW.showHdnLines) { + e.Draw(Entity::DrawAs::HIDDEN, canvas); + } + e.Draw(Entity::DrawAs::DEFAULT, canvas); } - glClear(GL_COLOR_BUFFER_BIT); - glClearDepth(1.0); - glClear(GL_DEPTH_BUFFER_BIT); - if(!SS.bgImage.pixmap.IsEmpty()) { - double mmw = SS.bgImage.pixmap.width / SS.bgImage.scale, - mmh = SS.bgImage.pixmap.height / SS.bgImage.scale; + // Draw filled paths in all groups, when those filled paths were requested + // specially by assigning a style with a fill color, or when the filled + // paths are just being filled by default. This should go last, to make + // the transparency work. + for(hGroup hg : SK.groupOrder) { + Group *g = SK.GetGroup(hg); + if(!(g->IsVisible())) continue; + g->DrawFilledPaths(canvas); + } +} +void GraphicsWindow::Draw(Canvas *canvas) { + const Camera &camera = canvas->GetCamera(); + + if(SS.bgImage.pixmap) { + double mmw = SS.bgImage.pixmap->width / SS.bgImage.scale, + mmh = SS.bgImage.pixmap->height / SS.bgImage.scale; + + Vector n = camera.projUp.Cross(camera.projRight); Vector origin = SS.bgImage.origin; - origin = origin.DotInToCsys(projRight, projUp, n); + origin = origin.DotInToCsys(camera.projRight, camera.projUp, n); // Place the depth of our origin at the point that corresponds to // w = 1, so that it's unaffected by perspective. origin.z = (offset.ScaledBy(-1)).Dot(n); - origin = origin.ScaleOutOfCsys(projRight, projUp, n); + origin = origin.ScaleOutOfCsys(camera.projRight, camera.projUp, n); - // Place the background at the very back of the Z order, though, by - // mucking with the depth range. - glDepthRange(1, 1); - ssglDrawPixmap(SS.bgImage.pixmap, - origin, - origin.Plus(projUp.ScaledBy(mmh)), - origin.Plus(projRight.ScaledBy(mmw).Plus( - projUp. ScaledBy(mmh))), - origin.Plus(projRight.ScaledBy(mmw))); + // Place the background at the very back of the Z order. + Canvas::Fill fillBackground = {}; + fillBackground.layer = Canvas::Layer::BACK; + Canvas::hFill hcfBackground = canvas->GetFill(fillBackground); + + canvas->DrawPixmap(SS.bgImage.pixmap, + origin, projRight.ScaledBy(mmw), projUp.ScaledBy(mmh), + { 0.0, 1.0 }, { 1.0, 0.0 }, + hcfBackground); } - ssglDepthRangeOffset(0); // Nasty case when we're reloading the linked files; could be that // we get an error, so a dialog pops up, and a message loop starts, and @@ -548,222 +605,74 @@ void GraphicsWindow::Paint() { // up, then we could trigger an oops trying to draw. if(!SS.allConsistent) return; - // Let's use two lights, at the user-specified locations - GLfloat f; - glEnable(GL_LIGHT0); - f = (GLfloat)SS.lightIntensity[0]; - GLfloat li0[] = { f, f, f, 1.0f }; - glLightfv(GL_LIGHT0, GL_DIFFUSE, li0); - glLightfv(GL_LIGHT0, GL_SPECULAR, li0); + if(showSnapGrid) DrawSnapGrid(canvas); - glEnable(GL_LIGHT1); - f = (GLfloat)SS.lightIntensity[1]; - GLfloat li1[] = { f, f, f, 1.0f }; - glLightfv(GL_LIGHT1, GL_DIFFUSE, li1); - glLightfv(GL_LIGHT1, GL_SPECULAR, li1); + // Draw all the things that don't change when we rotate. + DrawPersistent(canvas); - Vector ld; - ld = VectorFromProjs(SS.lightDir[0]); - GLfloat ld0[4] = { (GLfloat)ld.x, (GLfloat)ld.y, (GLfloat)ld.z, 0 }; - glLightfv(GL_LIGHT0, GL_POSITION, ld0); - ld = VectorFromProjs(SS.lightDir[1]); - GLfloat ld1[4] = { (GLfloat)ld.x, (GLfloat)ld.y, (GLfloat)ld.z, 0 }; - glLightfv(GL_LIGHT1, GL_POSITION, ld1); - - GLfloat ambient[4] = { (float)SS.ambientIntensity, - (float)SS.ambientIntensity, - (float)SS.ambientIntensity, 1 }; - glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient); - - ssglUnlockColor(); - - if(showSnapGrid && LockedInWorkplane()) { - hEntity he = ActiveWorkplane(); - EntityBase *wrkpl = SK.GetEntity(he), - *norm = wrkpl->Normal(); - Vector wu, wv, wn, wp; - wp = SK.GetEntity(wrkpl->point[0])->PointGetNum(); - wu = norm->NormalU(); - wv = norm->NormalV(); - wn = norm->NormalN(); - - double g = SS.gridSpacing; - - double umin = VERY_POSITIVE, umax = VERY_NEGATIVE, - vmin = VERY_POSITIVE, vmax = VERY_NEGATIVE; - int a; - for(a = 0; a < 4; a++) { - // Ideally, we would just do +/- half the width and height; but - // allow some extra slop for rounding. - Vector horiz = projRight.ScaledBy((0.6*width)/scale + 2*g), - vert = projUp. ScaledBy((0.6*height)/scale + 2*g); - if(a == 2 || a == 3) horiz = horiz.ScaledBy(-1); - if(a == 1 || a == 3) vert = vert. ScaledBy(-1); - Vector tp = horiz.Plus(vert).Minus(offset); - - // Project the point into our grid plane, normal to the screen - // (not to the grid plane). If the plane is on edge then this is - // impossible so don't try to draw the grid. - bool parallel; - Vector tpp = Vector::AtIntersectionOfPlaneAndLine( - wn, wn.Dot(wp), - tp, tp.Plus(n), - ¶llel); - if(parallel) goto nogrid; - - tpp = tpp.Minus(wp); - double uu = tpp.Dot(wu), - vv = tpp.Dot(wv); - - umin = min(uu, umin); - umax = max(uu, umax); - vmin = min(vv, vmin); - vmax = max(vv, vmax); - } - - int i, j, i0, i1, j0, j1; - - i0 = (int)(umin / g); - i1 = (int)(umax / g); - j0 = (int)(vmin / g); - j1 = (int)(vmax / g); - - if(i0 > i1 || i1 - i0 > 400) goto nogrid; - if(j0 > j1 || j1 - j0 > 400) goto nogrid; - - ssglLineWidth(1); - ssglColorRGBa(Style::Color(Style::DATUM), 0.3); - glBegin(GL_LINES); - for(i = i0 + 1; i < i1; i++) { - ssglVertex3v(wp.Plus(wu.ScaledBy(i*g)).Plus(wv.ScaledBy(j0*g))); - ssglVertex3v(wp.Plus(wu.ScaledBy(i*g)).Plus(wv.ScaledBy(j1*g))); - } - for(j = j0 + 1; j < j1; j++) { - ssglVertex3v(wp.Plus(wu.ScaledBy(i0*g)).Plus(wv.ScaledBy(j*g))); - ssglVertex3v(wp.Plus(wu.ScaledBy(i1*g)).Plus(wv.ScaledBy(j*g))); - } - glEnd(); - - // Clear the depth buffer, so that the grid is at the very back of - // the Z order. - glClear(GL_DEPTH_BUFFER_BIT); -nogrid:; + // Draw the polygon errors. + if(SS.checkClosedContour) { + SK.GetGroup(activeGroup)->DrawPolyError(canvas); } - // Draw the active group; this does stuff like the mesh and edges. - (SK.GetGroup(activeGroup))->Draw(); - - // Now draw the entities. - if(SS.GW.showHdnLines) { - ssglDepthRangeOffset(2); - glDepthFunc(GL_GREATER); - Entity::DrawAll(/*drawAsHidden=*/true); - glDepthFunc(GL_LEQUAL); - } - ssglDepthRangeOffset(0); - Entity::DrawAll(/*drawAsHidden=*/false); - - // Draw filled paths in all groups, when those filled paths were requested - // specially by assigning a style with a fill color, or when the filled - // paths are just being filled by default. This should go last, to make - // the transparency work. - for(i = 0; i < SK.groupOrder.n; i++) { - Group *g = SK.GetGroup(SK.groupOrder.elem[i]); - if(!(g->IsVisible())) continue; - g->DrawFilledPaths(); - } - - - glDisable(GL_DEPTH_TEST); // Draw the constraints - for(i = 0; i < SK.constraint.n; i++) { - SK.constraint.elem[i].Draw(); + for(Constraint &c : SK.constraint) { + c.Draw(Constraint::DrawAs::DEFAULT, canvas); } // Draw the "pending" constraint, i.e. a constraint that would be - // placed on a line that is almost horizontal or vertical + // placed on a line that is almost horizontal or vertical. if(SS.GW.pending.operation == Pending::DRAGGING_NEW_LINE_POINT && SS.GW.pending.hasSuggestion) { Constraint c = {}; c.group = SS.GW.activeGroup; c.workplane = SS.GW.ActiveWorkplane(); c.type = SS.GW.pending.suggestion; - c.ptA = Entity::NO_ENTITY; - c.ptB = Entity::NO_ENTITY; c.entityA = SS.GW.pending.request.entity(0); - c.entityB = Entity::NO_ENTITY; - c.other = false; - c.other2 = false; - // Only draw. - c.Draw(); + c.Draw(Constraint::DrawAs::DEFAULT, canvas); } + Canvas::Stroke strokeAnalyze = {}; + strokeAnalyze.layer = Canvas::Layer::FRONT; + strokeAnalyze.color = Style::Color(Style::ANALYZE); + strokeAnalyze.width = Style::Width(Style::ANALYZE); + Canvas::hStroke hcsAnalyze = canvas->GetStroke(strokeAnalyze); + // Draw the traced path, if one exists - ssglLineWidth(Style::Width(Style::ANALYZE)); - ssglColorRGB(Style::Color(Style::ANALYZE)); - SContour *sc = &(SS.traced.path); - glBegin(GL_LINE_STRIP); - for(i = 0; i < sc->l.n; i++) { - ssglVertex3v(sc->l.elem[i].p); - } - glEnd(); + SEdgeList tracedEdges = {}; + SS.traced.path.MakeEdgesInto(&tracedEdges); + canvas->DrawEdges(tracedEdges, hcsAnalyze); + tracedEdges.Clear(); + + Canvas::Stroke strokeError = {}; + strokeError.layer = Canvas::Layer::FRONT; + strokeError.color = Style::Color(Style::DRAW_ERROR); + strokeError.width = 12; + Canvas::hStroke hcsError = canvas->GetStroke(strokeError); // And the naked edges, if the user did Analyze -> Show Naked Edges. - ssglDrawEdges(&(SS.nakedEdges), /*endpointsToo=*/true, { Style::DRAW_ERROR }); + canvas->DrawEdges(SS.nakedEdges, hcsError); // Then redraw whatever the mouse is hovering over, highlighted. - glDisable(GL_DEPTH_TEST); - ssglLockColorTo(Style::Color(Style::HOVERED)); - hover.Draw(); + hover.Draw(/*isHovered=*/true, canvas); + SK.GetGroup(activeGroup)->DrawMesh(Group::DrawMeshAs::HOVERED, canvas); // And finally draw the selection, same mechanism. - ssglLockColorTo(Style::Color(Style::SELECTED)); for(Selection *s = selection.First(); s; s = selection.NextAfter(s)) { - s->Draw(); + s->Draw(/*isHovered=*/false, canvas); } + SK.GetGroup(activeGroup)->DrawMesh(Group::DrawMeshAs::SELECTED, canvas); - ssglUnlockColor(); - - // If a marquee selection is in progress, then draw the selection - // rectangle, as an outline and a transparent fill. - if(pending.operation == Pending::DRAGGING_MARQUEE) { - Point2d begin = ProjectPoint(orig.marqueePoint); - double xmin = min(orig.mouse.x, begin.x), - xmax = max(orig.mouse.x, begin.x), - ymin = min(orig.mouse.y, begin.y), - ymax = max(orig.mouse.y, begin.y); - - Vector tl = UnProjectPoint(Point2d::From(xmin, ymin)), - tr = UnProjectPoint(Point2d::From(xmax, ymin)), - br = UnProjectPoint(Point2d::From(xmax, ymax)), - bl = UnProjectPoint(Point2d::From(xmin, ymax)); - - ssglLineWidth((GLfloat)1.3); - ssglColorRGB(Style::Color(Style::HOVERED)); - glBegin(GL_LINE_LOOP); - ssglVertex3v(tl); - ssglVertex3v(tr); - ssglVertex3v(br); - ssglVertex3v(bl); - glEnd(); - ssglColorRGBa(Style::Color(Style::HOVERED), 0.10); - glBegin(GL_QUADS); - ssglVertex3v(tl); - ssglVertex3v(tr); - ssglVertex3v(br); - ssglVertex3v(bl); - glEnd(); - } + Canvas::Stroke strokeDatum = {}; + strokeDatum.layer = Canvas::Layer::FRONT; + strokeDatum.color = Style::Color(Style::DATUM); + strokeDatum.width = 1; + Canvas::hStroke hcsDatum = canvas->GetStroke(strokeDatum); // An extra line, used to indicate the origin when rotating within the // plane of the monitor. if(SS.extraLine.draw) { - ssglLineWidth(1); - ssglLockColorTo(Style::Color(Style::DATUM)); - glBegin(GL_LINES); - ssglVertex3v(SS.extraLine.ptA); - ssglVertex3v(SS.extraLine.ptB); - glEnd(); + canvas->DrawLine(SS.extraLine.ptA, SS.extraLine.ptB, hcsDatum); } // A note to indicate the origin in the just-exported file. @@ -774,38 +683,106 @@ nogrid:; u = SS.justExportedInfo.u, v = SS.justExportedInfo.v; } else { - p = SS.GW.offset.ScaledBy(-1); - u = SS.GW.projRight; - v = SS.GW.projUp; + p = camera.offset.ScaledBy(-1); + u = camera.projRight; + v = camera.projUp; } - - ssglColorRGB(Style::Color(Style::DATUM)); - - ssglWriteText("previewing exported geometry; press Esc to return", - Style::DefaultTextHeight(), - p.Plus(u.ScaledBy(10/scale)).Plus(v.ScaledBy(10/scale)), - u, v, NULL, NULL); + canvas->DrawVectorText("previewing exported geometry; press Esc to return", + Style::DefaultTextHeight() / camera.scale, + p.Plus(u.ScaledBy(10/scale)).Plus(v.ScaledBy(10/scale)), u, v, + hcsDatum); if(SS.justExportedInfo.showOrigin) { - ssglLineWidth(1.5); - glBegin(GL_LINES); - ssglVertex3v(p.Plus(u.WithMagnitude(-15/scale))); - ssglVertex3v(p.Plus(u.WithMagnitude(30/scale))); - ssglVertex3v(p.Plus(v.WithMagnitude(-15/scale))); - ssglVertex3v(p.Plus(v.WithMagnitude(30/scale))); - glEnd(); - - ssglWriteText("(x, y) = (0, 0) for file just exported", - Style::DefaultTextHeight(), - p.Plus(u.ScaledBy(40/scale)).Plus( - v.ScaledBy(-(Style::DefaultTextHeight())/scale)), - u, v, NULL, NULL); + Vector um = p.Plus(u.WithMagnitude(-15/scale)), + up = p.Plus(u.WithMagnitude(30/scale)), + vm = p.Plus(v.WithMagnitude(-15/scale)), + vp = p.Plus(v.WithMagnitude(30/scale)); + canvas->DrawLine(um, up, hcsDatum); + canvas->DrawLine(vm, vp, hcsDatum); + canvas->DrawVectorText("(x, y) = (0, 0) for file just exported", + Style::DefaultTextHeight() / camera.scale, + p.Plus(u.ScaledBy(40/scale)).Plus( + v.ScaledBy(-(Style::DefaultTextHeight())/scale)), u, v, + hcsDatum); } } +} + +void GraphicsWindow::Paint() { + havePainted = true; + + auto renderStartTime = std::chrono::high_resolution_clock::now(); + + int w, h; + GetGraphicsWindowSize(&w, &h); + width = w; + height = h; + + Camera camera = GetCamera(); + + OpenGl1Renderer canvas = {}; + canvas.camera = camera; + canvas.lighting = GetLighting(); + + if(!SS.ActiveGroupsOkay()) { + // Draw a different background whenever we're having solve problems. + RgbaColor bgColor = Style::Color(Style::DRAW_ERROR); + bgColor = RgbaColor::FromFloat(0.4f*bgColor.redF(), + 0.4f*bgColor.greenF(), + 0.4f*bgColor.blueF()); + canvas.lighting.backgroundColor = bgColor; + // And show the text window, which has info to debug it + ForceTextWindowShown(); + } + canvas.BeginFrame(); + canvas.UpdateProjection(); + + Draw(&canvas); + + canvas.camera.LoadIdentity(); + canvas.UpdateProjection(); + + UiCanvas uiCanvas = {}; + uiCanvas.canvas = &canvas; + + // If a marquee selection is in progress, then draw the selection + // rectangle, as an outline and a transparent fill. + if(pending.operation == Pending::DRAGGING_MARQUEE) { + Point2d begin = ProjectPoint(orig.marqueePoint); + uiCanvas.DrawRect((int)orig.mouse.x, (int)begin.x, + (int)orig.mouse.y, (int)begin.y, + /*fillColor=*/Style::Color(Style::HOVERED).WithAlpha(25), + /*outlineColor=*/Style::Color(Style::HOVERED)); + } // And finally the toolbar. if(SS.showToolbar) { - ToolbarDraw(); + canvas.camera.offset = {}; + canvas.camera.offset.x = -(double)canvas.camera.width / 2.0; + canvas.camera.offset.y = -(double)canvas.camera.height / 2.0; + canvas.UpdateProjection(); + ToolbarDraw(&uiCanvas); } -} + // If we display UI elements, also display an fps counter. + if(SS.showToolbar) { + canvas.EndFrame(); + + auto renderEndTime = std::chrono::high_resolution_clock::now(); + std::chrono::duration renderTime = renderEndTime - renderStartTime; + + RgbaColor renderTimeColor; + if(1000 / renderTime.count() < 60) { + // We aim for a steady 60fps; draw the counter in red when we're slower. + renderTimeColor = { 255, 0, 0, 255 }; + } else { + renderTimeColor = { 255, 255, 255, 255 }; + } + uiCanvas.DrawBitmapText(ssprintf("rendered in %ld ms (%ld 1/s)", + (long)renderTime.count(), + (long)(1000/renderTime.count())), + 5, 5, renderTimeColor); + } + + canvas.EndFrame(); +} diff --git a/src/drawconstraint.cpp b/src/drawconstraint.cpp index cecd73cd..f69f260f 100644 --- a/src/drawconstraint.cpp +++ b/src/drawconstraint.cpp @@ -8,37 +8,6 @@ //----------------------------------------------------------------------------- #include "solvespace.h" -void Constraint::LineDrawOrGetDistance(Vector a, Vector b) { - if(dogd.drawing) { - hStyle hs = GetStyle(); - - if(dogd.sel) { - dogd.sel->AddEdge(a, b, hs.v); - } else { - if(Style::Width(hs) >= 3.0) { - ssglFatLine(a, b, Style::Width(hs) / SS.GW.scale); - } else { - glBegin(GL_LINE_STRIP); - ssglVertex3v(a); - ssglVertex3v(b); - glEnd(); - } - } - } else { - Point2d ap = SS.GW.ProjectPoint(a); - Point2d bp = SS.GW.ProjectPoint(b); - - double d = dogd.mp.DistanceToLine(ap, bp.Minus(ap), /*asSegment=*/true); - dogd.dmin = min(dogd.dmin, d); - } -} - -static void LineCallback(void *fndata, Vector a, Vector b) -{ - Constraint *c = (Constraint *)fndata; - c->LineDrawOrGetDistance(a, b); -} - std::string Constraint::Label() const { std::string result; if(type == Type::ANGLE) { @@ -67,13 +36,14 @@ std::string Constraint::Label() const { return result; } -void Constraint::DoLabel(Vector ref, Vector *labelPos, Vector gr, Vector gu) { - hStyle hs = GetStyle(); - double th = Style::TextHeight(hs); +void Constraint::DoLabel(Canvas *canvas, Canvas::hStroke hcs, + Vector ref, Vector *labelPos, Vector gr, Vector gu) { + const Camera &camera = canvas->GetCamera(); std::string s = Label(); - double swidth = ssglStrWidth(s, th), - sheight = ssglStrCapHeight(th); + double textHeight = Style::TextHeight(GetStyle()) / camera.scale; + double swidth = VectorFont::Builtin()->GetWidth(textHeight, s), + sheight = VectorFont::Builtin()->GetCapHeight(textHeight); // By default, the reference is from the center; but the style could // specify otherwise if one is present, and it could also specify a @@ -94,37 +64,22 @@ void Constraint::DoLabel(Vector ref, Vector *labelPos, Vector gr, Vector gu) { if(o & (uint32_t)Style::TextOrigin::TOP) ref = ref.Minus(gu.WithMagnitude(sheight/2)); } - if(labelPos) { - // labelPos is from the top left corner (for the text box used to - // edit things), but ref is from the center. - *labelPos = ref.Minus(gr.WithMagnitude(swidth/2)).Minus( - gu.WithMagnitude(sheight/2)); - } - - - if(dogd.drawing) { - ssglWriteTextRefCenter(s, th, ref, gr, gu, LineCallback, (void *)this); - } else { - double l = swidth/2 - sheight/2; - l = max(l, 5/SS.GW.scale); - Point2d a = SS.GW.ProjectPoint(ref.Minus(gr.WithMagnitude(l))); - Point2d b = SS.GW.ProjectPoint(ref.Plus (gr.WithMagnitude(l))); - double d = dogd.mp.DistanceToLine(a, b.Minus(a), /*asSegment=*/true); - - dogd.dmin = min(dogd.dmin, d - (th / 2)); - } + Vector o = ref.Minus(gr.WithMagnitude(swidth/2)).Minus( + gu.WithMagnitude(sheight/2)); + canvas->DrawVectorText(s, textHeight, o, gr.WithMagnitude(1), gu.WithMagnitude(1), hcs); + if(labelPos) *labelPos = o; } -void Constraint::StippledLine(Vector a, Vector b) { - glLineStipple(4, 0x5555); - glEnable(GL_LINE_STIPPLE); - LineDrawOrGetDistance(a, b); - glDisable(GL_LINE_STIPPLE); -} +void Constraint::DoProjectedPoint(Canvas *canvas, Canvas::hStroke hcs, Vector *r) { + const Camera &camera = canvas->GetCamera(); + + Canvas::Stroke strokeStippled = *canvas->strokes.FindById(hcs); + strokeStippled.stipplePattern = StipplePattern::SHORT_DASH; + strokeStippled.stippleScale = 4.0 / camera.scale; + Canvas::hStroke hcsStippled = canvas->GetStroke(strokeStippled); -void Constraint::DoProjectedPoint(Vector *r) { Vector p = r->ProjectInto(workplane); - StippledLine(p, *r); + canvas->DrawLine(p, *r, hcsStippled); *r = p; } @@ -136,21 +91,20 @@ void Constraint::DoProjectedPoint(Vector *r) { // the line to almost meet the box, and return either positive or negative, // depending whether that extension was from A or from B. //----------------------------------------------------------------------------- -int Constraint::DoLineTrimmedAgainstBox(Vector ref, Vector a, Vector b, bool extend) { - hStyle hs = GetStyle(); - double th = Style::TextHeight(hs); - Vector gu = SS.GW.projUp.WithMagnitude(1), - gr = SS.GW.projRight.WithMagnitude(1); - - double pixels = 1.0 / SS.GW.scale; - std::string s = Label(); - double swidth = ssglStrWidth(s, th) + 4*pixels, - sheight = ssglStrCapHeight(th) + 8*pixels; - - return DoLineTrimmedAgainstBox(ref, a, b, extend, gr, gu, swidth, sheight); +int Constraint::DoLineTrimmedAgainstBox(Canvas *canvas, Canvas::hStroke hcs, + Vector ref, Vector a, Vector b, bool extend) { + const Camera &camera = canvas->GetCamera(); + double th = Style::TextHeight(GetStyle()) / camera.scale; + double pixels = 1.0 / camera.scale; + double swidth = VectorFont::Builtin()->GetWidth(th, Label()) + 4 * pixels, + sheight = VectorFont::Builtin()->GetCapHeight(th) + 8 * pixels; + Vector gu = camera.projUp.WithMagnitude(1), + gr = camera.projRight.WithMagnitude(1); + return DoLineTrimmedAgainstBox(canvas, hcs, ref, a, b, extend, gr, gu, swidth, sheight); } -int Constraint::DoLineTrimmedAgainstBox(Vector ref, Vector a, Vector b, bool extend, +int Constraint::DoLineTrimmedAgainstBox(Canvas *canvas, Canvas::hStroke hcs, + Vector ref, Vector a, Vector b, bool extend, Vector gr, Vector gu, double swidth, double sheight) { struct { Vector n; @@ -188,20 +142,20 @@ int Constraint::DoLineTrimmedAgainstBox(Vector ref, Vector a, Vector b, bool ext // Both in range; so there's pieces of the line on both sides of the label box. if(tmin >= 0.0 && tmin <= 1.0 && tmax >= 0.0 && tmax <= 1.0) { - LineDrawOrGetDistance(a, a.Plus(dl.ScaledBy(tmin))); - LineDrawOrGetDistance(a.Plus(dl.ScaledBy(tmax)), b); + canvas->DrawLine(a, a.Plus(dl.ScaledBy(tmin)), hcs); + canvas->DrawLine(a.Plus(dl.ScaledBy(tmax)), b, hcs); return 0; } // Only one intersection in range; so the box is right on top of the endpoint if(tmin >= 0.0 && tmin <= 1.0) { - LineDrawOrGetDistance(a, a.Plus(dl.ScaledBy(tmin))); + canvas->DrawLine(a, a.Plus(dl.ScaledBy(tmin)), hcs); return 0; } // Likewise. if(tmax >= 0.0 && tmax <= 1.0) { - LineDrawOrGetDistance(a.Plus(dl.ScaledBy(tmax)), b); + canvas->DrawLine(a.Plus(dl.ScaledBy(tmax)), b, hcs); return 0; } @@ -211,13 +165,13 @@ int Constraint::DoLineTrimmedAgainstBox(Vector ref, Vector a, Vector b, bool ext // and closer to b, positive means outside and closer to a. if(tmax < 0.0) { if(extend) a = a.Plus(dl.ScaledBy(tmax)); - LineDrawOrGetDistance(a, b); + canvas->DrawLine(a, b, hcs); return 1; } if(tmin > 1.0) { if(extend) b = a.Plus(dl.ScaledBy(tmin)); - LineDrawOrGetDistance(a, b); + canvas->DrawLine(a, b, hcs); return -1; } @@ -225,11 +179,12 @@ int Constraint::DoLineTrimmedAgainstBox(Vector ref, Vector a, Vector b, bool ext return 0; } -void Constraint::DoArrow(Vector p, Vector dir, Vector n, double width, double angle, double da) { +void Constraint::DoArrow(Canvas *canvas, Canvas::hStroke hcs, + Vector p, Vector dir, Vector n, double width, double angle, double da) { dir = dir.WithMagnitude(width / cos(angle)); dir = dir.RotatedAbout(n, da); - LineDrawOrGetDistance(p, p.Plus(dir.RotatedAbout(n, angle))); - LineDrawOrGetDistance(p, p.Plus(dir.RotatedAbout(n, -angle))); + canvas->DrawLine(p, p.Plus(dir.RotatedAbout(n, angle)), hcs); + canvas->DrawLine(p, p.Plus(dir.RotatedAbout(n, -angle)), hcs); } //----------------------------------------------------------------------------- @@ -239,10 +194,12 @@ void Constraint::DoArrow(Vector p, Vector dir, Vector n, double width, double an // to the line AB, until the line between the extensions crosses ref (the // center of the label). //----------------------------------------------------------------------------- -void Constraint::DoLineWithArrows(Vector ref, Vector a, Vector b, +void Constraint::DoLineWithArrows(Canvas *canvas, Canvas::hStroke hcs, + Vector ref, Vector a, Vector b, bool onlyOneExt) { - double pixels = 1.0 / SS.GW.scale; + const Camera &camera = canvas->GetCamera(); + double pixels = 1.0 / camera.scale; Vector ab = a.Minus(b); Vector ar = a.Minus(ref); @@ -258,15 +215,15 @@ void Constraint::DoLineWithArrows(Vector ref, Vector a, Vector b, // Extension lines extend 10 pixels beyond where the arrows get // drawn (which is at the same offset perpendicular from AB as the // label). - LineDrawOrGetDistance(a, ae.Plus(out.WithMagnitude(10*pixels))); + canvas->DrawLine(a, ae.Plus(out.WithMagnitude(10*pixels)), hcs); if(!onlyOneExt) { - LineDrawOrGetDistance(b, be.Plus(out.WithMagnitude(10*pixels))); + canvas->DrawLine(b, be.Plus(out.WithMagnitude(10*pixels)), hcs); } else { Vector prj = be; - DoProjectedPoint(&prj); + DoProjectedPoint(canvas, hcs, &prj); } - int within = DoLineTrimmedAgainstBox(ref, ae, be); + int within = DoLineTrimmedAgainstBox(canvas, hcs, ref, ae, be); // Arrow heads are 13 pixels long, with an 18 degree half-angle. double theta = 18*PI/180; @@ -275,24 +232,29 @@ void Constraint::DoLineWithArrows(Vector ref, Vector a, Vector b, if(within != 0) { arrow = arrow.ScaledBy(-1); Vector seg = (be.Minus(ae)).WithMagnitude(18*pixels); - if(within < 0) LineDrawOrGetDistance(ae, ae.Minus(seg)); - if(within > 0) LineDrawOrGetDistance(be, be.Plus(seg)); + if(within < 0) canvas->DrawLine(ae, ae.Minus(seg), hcs); + if(within > 0) canvas->DrawLine(be, be.Plus(seg), hcs); } - DoArrow(ae, arrow, n, 13.0 * pixels, theta, 0.0); - DoArrow(be, arrow.Negated(), n, 13.0 * pixels, theta, 0.0); + DoArrow(canvas, hcs, ae, arrow, n, 13.0 * pixels, theta, 0.0); + DoArrow(canvas, hcs, be, arrow.Negated(), n, 13.0 * pixels, theta, 0.0); } -void Constraint::DoEqualLenTicks(Vector a, Vector b, Vector gn, Vector *refp) { +void Constraint::DoEqualLenTicks(Canvas *canvas, Canvas::hStroke hcs, + Vector a, Vector b, Vector gn, Vector *refp) { + const Camera &camera = canvas->GetCamera(); + Vector m = (a.ScaledBy(1.0/3)).Plus(b.ScaledBy(2.0/3)); if(refp) *refp = m; Vector ab = a.Minus(b); - Vector n = (gn.Cross(ab)).WithMagnitude(10/SS.GW.scale); + Vector n = (gn.Cross(ab)).WithMagnitude(10/camera.scale); - LineDrawOrGetDistance(m.Minus(n), m.Plus(n)); + canvas->DrawLine(m.Minus(n), m.Plus(n), hcs); } -void Constraint::DoEqualRadiusTicks(hEntity he, Vector *refp) { +void Constraint::DoEqualRadiusTicks(Canvas *canvas, Canvas::hStroke hcs, + hEntity he, Vector *refp) { + const Camera &camera = canvas->GetCamera(); Entity *circ = SK.GetEntity(he); Vector center = SK.GetEntity(circ->point[0])->PointGetNum(); @@ -313,15 +275,18 @@ void Constraint::DoEqualRadiusTicks(hEntity he, Vector *refp) { d = d.ScaledBy(r); Vector p = center.Plus(d); if(refp) *refp = p; - Vector tick = d.WithMagnitude(10/SS.GW.scale); - LineDrawOrGetDistance(p.Plus(tick), p.Minus(tick)); + Vector tick = d.WithMagnitude(10/camera.scale); + canvas->DrawLine(p.Plus(tick), p.Minus(tick), hcs); } -void Constraint::DoArcForAngle(Vector a0, Vector da, Vector b0, Vector db, - Vector offset, Vector *ref, bool trim) +void Constraint::DoArcForAngle(Canvas *canvas, Canvas::hStroke hcs, + Vector a0, Vector da, Vector b0, Vector db, + Vector offset, Vector *ref, bool trim) { - Vector gr = SS.GW.projRight.ScaledBy(1.0); - Vector gu = SS.GW.projUp.ScaledBy(1.0); + const Camera &camera = canvas->GetCamera(); + double pixels = 1.0 / camera.scale; + Vector gr = camera.projRight.ScaledBy(1.0); + Vector gu = camera.projUp.ScaledBy(1.0); if(workplane.v != Entity::FREE_IN_3D.v) { a0 = a0.ProjectInto(workplane); @@ -330,7 +295,6 @@ void Constraint::DoArcForAngle(Vector a0, Vector da, Vector b0, Vector db, db = db.ProjectVectorInto(workplane); } - double px = 1.0 / SS.GW.scale; Vector a1 = a0.Plus(da); Vector b1 = b0.Plus(db); @@ -368,29 +332,27 @@ void Constraint::DoArcForAngle(Vector a0, Vector da, Vector b0, Vector db, double rda = rm.Dot(da), rdna = rm.Dot(dna); // Introduce minimal arc radius in pixels - double r = max(sqrt(rda*rda + rdna*rdna), 15.0 * px); + double r = max(sqrt(rda*rda + rdna*rdna), 15.0 * pixels); - hStyle hs = disp.style; - if(hs.v == 0) hs.v = Style::CONSTRAINT; - double th = Style::TextHeight(hs); - double swidth = ssglStrWidth(Label(), th) + 4.0 * px; - double sheight = ssglStrCapHeight(th) + 8.0 * px; + double th = Style::TextHeight(GetStyle()) / camera.scale; + double swidth = VectorFont::Builtin()->GetWidth(th, Label()) + 4*pixels, + sheight = VectorFont::Builtin()->GetCapHeight(th) + 8*pixels; double textR = sqrt(swidth * swidth + sheight * sheight) / 2.0; - *ref = pi.Plus(rm.WithMagnitude(std::max(rm.Magnitude(), 15 * px + textR))); + *ref = pi.Plus(rm.WithMagnitude(std::max(rm.Magnitude(), 15 * pixels + textR))); // Arrow points Vector apa = da. ScaledBy(r).Plus(pi); Vector apb = da. ScaledBy(r*cos(thetaf)).Plus( dna.ScaledBy(r*sin(thetaf))).Plus(pi); - double arrowW = 13 * px; + double arrowW = 13 * pixels; double arrowA = 18.0 * PI / 180.0; bool arrowVisible = apb.Minus(apa).Magnitude() > 2.5 * arrowW; // Arrow reversing indicator bool arrowRev = false; // The minimal extension length in angular representation - double extAngle = 18 * px / r; + double extAngle = 18 * pixels / r; // Arc additional angle double addAngle = 0.0; @@ -435,16 +397,17 @@ void Constraint::DoArcForAngle(Vector a0, Vector da, Vector b0, Vector db, dna.ScaledBy(r*sin(theta))).Plus(pi); if(i > 0) { if(trim) { - DoLineTrimmedAgainstBox(*ref, prev, p, /*extend=*/false, gr, gu, swidth, sheight); + DoLineTrimmedAgainstBox(canvas, hcs, *ref, prev, p, + /*extend=*/false, gr, gu, swidth, sheight); } else { - LineDrawOrGetDistance(prev, p); + canvas->DrawLine(prev, p, hcs); } } prev = p; } - DoLineExtend(a0, a1, apa, 5.0 * px); - DoLineExtend(b0, b1, apb, 5.0 * px); + DoLineExtend(canvas, hcs, a0, a1, apa, 5.0 * pixels); + DoLineExtend(canvas, hcs, b0, b1, apb, 5.0 * pixels); // Draw arrows only when we have enough space. if(arrowVisible) { @@ -453,18 +416,21 @@ void Constraint::DoArcForAngle(Vector a0, Vector da, Vector b0, Vector db, dna = dna.ScaledBy(-1.0); angleCorr = -angleCorr; } - DoArrow(apa, dna, norm, arrowW, arrowA, angleCorr); - DoArrow(apb, dna, norm, arrowW, arrowA, thetaf + PI - angleCorr); + DoArrow(canvas, hcs, apa, dna, norm, arrowW, arrowA, angleCorr); + DoArrow(canvas, hcs, apb, dna, norm, arrowW, arrowA, thetaf + PI - angleCorr); } } else { // The lines are skew; no wonderful way to illustrate that. + *ref = a0.Plus(b0); *ref = (*ref).ScaledBy(0.5).Plus(disp.offset); gu = gu.WithMagnitude(1); + double textHeight = Style::TextHeight(GetStyle()) / camera.scale; Vector trans = - (*ref).Plus(gu.ScaledBy(-1.5*ssglStrCapHeight(Style::DefaultTextHeight()))); - ssglWriteTextRefCenter("angle between skew lines", Style::DefaultTextHeight(), - trans, gr.WithMagnitude(px), gu.WithMagnitude(px), LineCallback, (void *)this); + (*ref).Plus(gu.ScaledBy(-1.5*VectorFont::Builtin()->GetCapHeight(textHeight))); + canvas->DrawVectorText("angle between skew lines", textHeight, + trans, gr.WithMagnitude(pixels), gu.WithMagnitude(pixels), + hcs); } } @@ -486,13 +452,14 @@ bool Constraint::IsVisible() const { return true; } -bool Constraint::DoLineExtend(Vector p0, Vector p1, Vector pt, double salient) { +bool Constraint::DoLineExtend(Canvas *canvas, Canvas::hStroke hcs, + Vector p0, Vector p1, Vector pt, double salient) { Vector dir = p1.Minus(p0); double k = dir.Dot(pt.Minus(p0)) / dir.Dot(dir); Vector ptOnLine = p0.Plus(dir.ScaledBy(k)); // Draw projection line. - LineDrawOrGetDistance(pt, ptOnLine); + canvas->DrawLine(pt, ptOnLine, hcs); // Calculate salient direction. Vector sd = dir.WithMagnitude(1.0).ScaledBy(salient); @@ -511,18 +478,42 @@ bool Constraint::DoLineExtend(Vector p0, Vector p1, Vector pt, double salient) { } // Draw extension line. - LineDrawOrGetDistance(from, to); + canvas->DrawLine(from, to, hcs); return true; } -void Constraint::DrawOrGetDistance(Vector *labelPos, Vector *refps) { +void Constraint::DoLayout(DrawAs how, Canvas *canvas, + Vector *labelPos, std::vector *refs) { if(!IsVisible()) return; // Unit vectors that describe our current view of the scene. One pixel // long, not one actual unit. - Vector gr = SS.GW.projRight.ScaledBy(1/SS.GW.scale); - Vector gu = SS.GW.projUp.ScaledBy(1/SS.GW.scale); - Vector gn = (gr.Cross(gu)).WithMagnitude(1/SS.GW.scale); + const Camera &camera = canvas->GetCamera(); + Vector gr = camera.projRight.ScaledBy(1/camera.scale); + Vector gu = camera.projUp.ScaledBy(1/camera.scale); + Vector gn = (gr.Cross(gu)).WithMagnitude(1/camera.scale); + + double textHeight = Style::TextHeight(GetStyle()) / camera.scale; + + RgbaColor color = {}; + switch(how) { + case DrawAs::DEFAULT: color = Style::Color(GetStyle()); break; + case DrawAs::HOVERED: color = Style::Color(Style::HOVERED); break; + case DrawAs::SELECTED: color = Style::Color(Style::SELECTED); break; + } + + Canvas::Stroke stroke = {}; + stroke.layer = Canvas::Layer::FRONT; + stroke.color = color; + stroke.width = Style::Width(GetStyle()); + stroke.zIndex = 4; + Canvas::hStroke hcs = canvas->GetStroke(stroke); + + Canvas::Fill fill = {}; + fill.layer = Canvas::Layer::FRONT; + fill.color = color; + fill.zIndex = stroke.zIndex; + Canvas::hFill hcf = canvas->GetFill(fill); switch(type) { case Type::PT_PT_DISTANCE: { @@ -530,35 +521,40 @@ void Constraint::DrawOrGetDistance(Vector *labelPos, Vector *refps) { Vector bp = SK.GetEntity(ptB)->PointGetNum(); if(workplane.v != Entity::FREE_IN_3D.v) { - DoProjectedPoint(&ap); - DoProjectedPoint(&bp); + DoProjectedPoint(canvas, hcs, &ap); + DoProjectedPoint(canvas, hcs, &bp); } Vector ref = ((ap.Plus(bp)).ScaledBy(0.5)).Plus(disp.offset); - if(refps) refps[0] = refps[1] = ref; + if(refs) refs->push_back(ref); - DoLineWithArrows(ref, ap, bp, /*onlyOneExt=*/false); - DoLabel(ref, labelPos, gr, gu); + DoLineWithArrows(canvas, hcs, ref, ap, bp, /*onlyOneExt=*/false); + DoLabel(canvas, hcs, ref, labelPos, gr, gu); return; } case Type::PROJ_PT_DISTANCE: { + Canvas::Stroke strokeStippled = stroke; + strokeStippled.stipplePattern = StipplePattern::SHORT_DASH; + strokeStippled.stippleScale = 4.0 / camera.scale; + Canvas::hStroke hcsStippled = canvas->GetStroke(strokeStippled); + Vector ap = SK.GetEntity(ptA)->PointGetNum(), bp = SK.GetEntity(ptB)->PointGetNum(), dp = (bp.Minus(ap)), pp = SK.GetEntity(entityA)->VectorGetNum(); Vector ref = ((ap.Plus(bp)).ScaledBy(0.5)).Plus(disp.offset); - if(refps) refps[0] = refps[1] = ref; + if(refs) refs->push_back(ref); pp = pp.WithMagnitude(1); double d = dp.Dot(pp); Vector bpp = ap.Plus(pp.ScaledBy(d)); - StippledLine(ap, bpp); - StippledLine(bp, bpp); + canvas->DrawLine(ap, bpp, hcsStippled); + canvas->DrawLine(bp, bpp, hcsStippled); - DoLineWithArrows(ref, ap, bpp, /*onlyOneExt=*/false); - DoLabel(ref, labelPos, gr, gu); + DoLineWithArrows(canvas, hcs, ref, ap, bpp, /*onlyOneExt=*/false); + DoLabel(canvas, hcs, ref, labelPos, gr, gu); return; } @@ -579,13 +575,13 @@ void Constraint::DrawOrGetDistance(Vector *labelPos, Vector *refps) { Vector closest = pt.Plus(n.WithMagnitude(d)); Vector ref = ((closest.Plus(pt)).ScaledBy(0.5)).Plus(disp.offset); - if(refps) refps[0] = refps[1] = ref; + if(refs) refs->push_back(ref); if(!pt.Equals(closest)) { - DoLineWithArrows(ref, pt, closest, /*onlyOneExt=*/true); + DoLineWithArrows(canvas, hcs, ref, pt, closest, /*onlyOneExt=*/true); } - DoLabel(ref, labelPos, gr, gu); + DoLabel(canvas, hcs, ref, labelPos, gr, gu); return; } @@ -599,29 +595,29 @@ void Constraint::DrawOrGetDistance(Vector *labelPos, Vector *refps) { if(workplane.v != Entity::FREE_IN_3D.v) { lA = lA.ProjectInto(workplane); lB = lB.ProjectInto(workplane); - DoProjectedPoint(&pt); + DoProjectedPoint(canvas, hcs, &pt); } // Find the closest point on the line Vector closest = pt.ClosestPointOnLine(lA, dl); Vector ref = ((closest.Plus(pt)).ScaledBy(0.5)).Plus(disp.offset); - if(refps) refps[0] = refps[1] = ref; - DoLabel(ref, labelPos, gr, gu); + if(refs) refs->push_back(ref); + DoLabel(canvas, hcs, ref, labelPos, gr, gu); if(!pt.Equals(closest)) { - DoLineWithArrows(ref, pt, closest, /*onlyOneExt=*/true); + DoLineWithArrows(canvas, hcs, ref, pt, closest, /*onlyOneExt=*/true); // Extensions to line - double pixels = 1.0 / SS.GW.scale; + double pixels = 1.0 / camera.scale; Vector refClosest = ref.ClosestPointOnLine(lA, dl); double ddl = dl.Dot(dl); if(fabs(ddl) > LENGTH_EPS * LENGTH_EPS) { double t = refClosest.Minus(lA).Dot(dl) / ddl; if(t < 0.0) { - LineDrawOrGetDistance(refClosest.Minus(dl.WithMagnitude(10.0 * pixels)), lA); + canvas->DrawLine(refClosest.Minus(dl.WithMagnitude(10.0 * pixels)), lA, hcs); } else if(t > 1.0) { - LineDrawOrGetDistance(refClosest.Plus(dl.WithMagnitude(10.0 * pixels)), lB); + canvas->DrawLine(refClosest.Plus(dl.WithMagnitude(10.0 * pixels)), lB, hcs); } } } @@ -636,7 +632,7 @@ void Constraint::DrawOrGetDistance(Vector *labelPos, Vector *refps) { Vector lB = SK.GetEntity(line->point[1])->PointGetNum(); Vector c2 = (lA.ScaledBy(1-t)).Plus(lB.ScaledBy(t)); - DoProjectedPoint(&c2); + DoProjectedPoint(canvas, hcs, &c2); } return; } @@ -651,38 +647,25 @@ void Constraint::DrawOrGetDistance(Vector *labelPos, Vector *refps) { Vector ref = center.Plus(disp.offset); // Force the label into the same plane as the circle. ref = ref.Minus(n.ScaledBy(n.Dot(ref) - n.Dot(center))); - if(refps) refps[0] = refps[1] = ref; + if(refs) refs->push_back(ref); Vector mark = ref.Minus(center); mark = mark.WithMagnitude(mark.Magnitude()-r); - DoLineTrimmedAgainstBox(ref, ref, ref.Minus(mark)); + DoLineTrimmedAgainstBox(canvas, hcs, ref, ref, ref.Minus(mark)); Vector topLeft; - DoLabel(ref, &topLeft, gr, gu); + DoLabel(canvas, hcs, ref, &topLeft, gr, gu); if(labelPos) *labelPos = topLeft; return; } case Type::POINTS_COINCIDENT: { - if(!dogd.drawing) { - for(int i = 0; i < 2; i++) { - Vector p = SK.GetEntity(i == 0 ? ptA : ptB)-> PointGetNum(); - if(refps) refps[i] = p; - Point2d pp = SS.GW.ProjectPoint(p); - // The point is selected within a radius of 7, from the - // same center; so if the point is visible, then this - // constraint cannot be selected. But that's okay. - dogd.dmin = min(dogd.dmin, pp.DistanceTo(dogd.mp) - 3); - } - return; - } - - if(dogd.drawing) { + if(how == DrawAs::DEFAULT) { // Let's adjust the color of this constraint to have the same // rough luma as the point color, so that the constraint does not // stand out in an ugly way. RgbaColor cd = Style::Color(Style::DATUM), - cc = Style::Color(Style::CONSTRAINT); + cc = Style::Color(Style::CONSTRAINT); // convert from 8-bit color to a vector Vector vd = Vector::From(cd.redF(), cd.greenF(), cd.blueF()), vc = Vector::From(cc.redF(), cc.greenF(), cc.blueF()); @@ -690,22 +673,23 @@ void Constraint::DrawOrGetDistance(Vector *labelPos, Vector *refps) { // the datum color, maybe a bit dimmer vc = vc.WithMagnitude(vd.Magnitude()*0.9); // and set the color to that. - ssglColorRGB(RGBf(vc.x, vc.y, vc.z)); - - for(int a = 0; a < 2; a++) { - Vector r = SS.GW.projRight.ScaledBy((a+1)/SS.GW.scale); - Vector d = SS.GW.projUp.ScaledBy((2-a)/SS.GW.scale); - for(int i = 0; i < 2; i++) { - Vector p = SK.GetEntity(i == 0 ? ptA : ptB)-> PointGetNum(); - glBegin(GL_QUADS); - ssglVertex3v(p.Plus (r).Plus (d)); - ssglVertex3v(p.Plus (r).Minus(d)); - ssglVertex3v(p.Minus(r).Minus(d)); - ssglVertex3v(p.Minus(r).Plus (d)); - glEnd(); - } + fill.color = RGBf(vc.x, vc.y, vc.z); + hcf = canvas->GetFill(fill); + } + for(int a = 0; a < 2; a++) { + Vector r = camera.projRight.ScaledBy((a+1)/camera.scale); + Vector d = camera.projUp.ScaledBy((2-a)/camera.scale); + for(int i = 0; i < 2; i++) { + Vector p = SK.GetEntity(i == 0 ? ptA : ptB)-> PointGetNum(); + if(refs) refs->push_back(p); + canvas->DrawQuad(p.Plus (r).Plus (d), + p.Plus (r).Minus(d), + p.Minus(r).Minus(d), + p.Minus(r).Plus (d), + hcf); } + } return; } @@ -714,9 +698,9 @@ void Constraint::DrawOrGetDistance(Vector *labelPos, Vector *refps) { case Type::PT_ON_LINE: case Type::PT_ON_FACE: case Type::PT_IN_PLANE: { - double s = 8/SS.GW.scale; + double s = 8/camera.scale; Vector p = SK.GetEntity(ptA)->PointGetNum(); - if(refps) refps[0] = refps[1] = p; + if(refs) refs->push_back(p); Vector r, d; if(type == Type::PT_ON_FACE) { Vector n = SK.GetEntity(entityA)->FaceGetNormalNum(); @@ -732,26 +716,26 @@ void Constraint::DrawOrGetDistance(Vector *labelPos, Vector *refps) { s *= (6.0/8); // draw these a little smaller } r = r.WithMagnitude(s); d = d.WithMagnitude(s); - LineDrawOrGetDistance(p.Plus (r).Plus (d), p.Plus (r).Minus(d)); - LineDrawOrGetDistance(p.Plus (r).Minus(d), p.Minus(r).Minus(d)); - LineDrawOrGetDistance(p.Minus(r).Minus(d), p.Minus(r).Plus (d)); - LineDrawOrGetDistance(p.Minus(r).Plus (d), p.Plus (r).Plus (d)); + canvas->DrawLine(p.Plus (r).Plus (d), p.Plus (r).Minus(d), hcs); + canvas->DrawLine(p.Plus (r).Minus(d), p.Minus(r).Minus(d), hcs); + canvas->DrawLine(p.Minus(r).Minus(d), p.Minus(r).Plus (d), hcs); + canvas->DrawLine(p.Minus(r).Plus (d), p.Plus (r).Plus (d), hcs); return; } case Type::WHERE_DRAGGED: { Vector p = SK.GetEntity(ptA)->PointGetNum(); - if(refps) refps[0] = refps[1] = p; - Vector u = p.Plus(gu.WithMagnitude(8/SS.GW.scale)).Plus( - gr.WithMagnitude(8/SS.GW.scale)), - uu = u.Minus(gu.WithMagnitude(5/SS.GW.scale)), - ur = u.Minus(gr.WithMagnitude(5/SS.GW.scale)); + if(refs) refs->push_back(p); + Vector u = p.Plus(gu.WithMagnitude(8/camera.scale)).Plus( + gr.WithMagnitude(8/camera.scale)), + uu = u.Minus(gu.WithMagnitude(5/camera.scale)), + ur = u.Minus(gr.WithMagnitude(5/camera.scale)); // Draw four little crop marks, uniformly spaced (by ninety // degree rotations) around the point. int i; for(i = 0; i < 4; i++) { - LineDrawOrGetDistance(u, uu); - LineDrawOrGetDistance(u, ur); + canvas->DrawLine(u, uu, hcs); + canvas->DrawLine(u, ur, hcs); u = u.RotatedAbout(p, gn, PI/2); ur = ur.RotatedAbout(p, gn, PI/2); uu = uu.RotatedAbout(p, gn, PI/2); @@ -763,14 +747,14 @@ void Constraint::DrawOrGetDistance(Vector *labelPos, Vector *refps) { for(int i = 0; i < 2; i++) { Entity *e = SK.GetEntity(i == 0 ? entityA : entityB); Quaternion q = e->NormalGetNum(); - Vector n = q.RotationN().WithMagnitude(25/SS.GW.scale); - Vector u = q.RotationU().WithMagnitude(6/SS.GW.scale); + Vector n = q.RotationN().WithMagnitude(25/camera.scale); + Vector u = q.RotationU().WithMagnitude(6/camera.scale); Vector p = SK.GetEntity(e->point[0])->PointGetNum(); - p = p.Plus(n.WithMagnitude(10/SS.GW.scale)); - if(refps) refps[i] = p; + p = p.Plus(n.WithMagnitude(10/camera.scale)); + if(refs) refs->push_back(p); - LineDrawOrGetDistance(p.Plus(u), p.Minus(u).Plus(n)); - LineDrawOrGetDistance(p.Minus(u), p.Plus(u).Plus(n)); + canvas->DrawLine(p.Plus(u), p.Minus(u).Plus(n), hcs); + canvas->DrawLine(p.Minus(u), p.Plus(u).Plus(n), hcs); } return; } @@ -796,12 +780,12 @@ void Constraint::DrawOrGetDistance(Vector *labelPos, Vector *refps) { da = da.ScaledBy(-1); } - DoArcForAngle(a0, da, b0, db, - da.WithMagnitude(40/SS.GW.scale), &ref, /*trim=*/false); - if(refps) refps[0] = ref; - DoArcForAngle(c0, dc, d0, dd, - dc.WithMagnitude(40/SS.GW.scale), &ref, /*trim=*/false); - if(refps) refps[1] = ref; + DoArcForAngle(canvas, hcs, a0, da, b0, db, + da.WithMagnitude(40/camera.scale), &ref, /*trim=*/false); + if(refs) refs->push_back(ref); + DoArcForAngle(canvas, hcs, c0, dc, d0, dd, + dc.WithMagnitude(40/camera.scale), &ref, /*trim=*/false); + if(refs) refs->push_back(ref); return; } @@ -820,9 +804,9 @@ void Constraint::DrawOrGetDistance(Vector *labelPos, Vector *refps) { } Vector ref; - DoArcForAngle(a0, da, b0, db, disp.offset, &ref, /*trim=*/true); - DoLabel(ref, labelPos, gr, gu); - if(refps) refps[0] = refps[1] = ref; + DoArcForAngle(canvas, hcs, a0, da, b0, db, disp.offset, &ref, /*trim=*/true); + DoLabel(canvas, hcs, ref, labelPos, gr, gu); + if(refs) refs->push_back(ref); return; } @@ -845,8 +829,8 @@ void Constraint::DrawOrGetDistance(Vector *labelPos, Vector *refps) { // Calculate orientation of perpendicular sign only // once, so that it's the same both times it's drawn u = e->VectorGetNum(); - u = u.WithMagnitude(16/SS.GW.scale); - v = (rn.Cross(u)).WithMagnitude(16/SS.GW.scale); + u = u.WithMagnitude(16/camera.scale); + v = (rn.Cross(u)).WithMagnitude(16/camera.scale); // a bit of bias to stop it from flickering between the // two possibilities if(fabs(u.Dot(ru)) < fabs(v.Dot(ru)) + LENGTH_EPS) { @@ -857,10 +841,10 @@ void Constraint::DrawOrGetDistance(Vector *labelPos, Vector *refps) { Vector p = e->VectorGetRefPoint(); Vector s = p.Plus(u).Plus(v); - LineDrawOrGetDistance(s, s.Plus(v)); + canvas->DrawLine(s, s.Plus(v), hcs); Vector m = s.Plus(v.ScaledBy(0.5)); - LineDrawOrGetDistance(m, m.Plus(u)); - if(refps) refps[i] = m; + canvas->DrawLine(m, m.Plus(u), hcs); + if(refs) refs->push_back(m); } return; } @@ -877,7 +861,7 @@ void Constraint::DrawOrGetDistance(Vector *labelPos, Vector *refps) { Vector p = SK.GetEntity(arc->point[other ? 2 : 1])->PointGetNum(); Vector r = p.Minus(c); - textAt = p.Plus(r.WithMagnitude(14/SS.GW.scale)); + textAt = p.Plus(r.WithMagnitude(14/camera.scale)); u = norm->NormalU(); v = norm->NormalV(); } else if(type == Type::CUBIC_LINE_TANGENT) { @@ -898,7 +882,7 @@ void Constraint::DrawOrGetDistance(Vector *labelPos, Vector *refps) { cubic->CubicGetStartNum(); Vector dir = SK.GetEntity(entityB)->VectorGetNum(); Vector out = n.Cross(dir); - textAt = p.Plus(out.WithMagnitude(14/SS.GW.scale)); + textAt = p.Plus(out.WithMagnitude(14/camera.scale)); } else { Vector n, dir; EntityBase *wn = SK.GetEntity(workplane)->Normal(); @@ -929,17 +913,12 @@ void Constraint::DrawOrGetDistance(Vector *labelPos, Vector *refps) { } } dir = n.Cross(dir); - textAt = textAt.Plus(dir.WithMagnitude(14/SS.GW.scale)); + textAt = textAt.Plus(dir.WithMagnitude(14/camera.scale)); } - if(dogd.drawing) { - ssglWriteTextRefCenter("T", Style::DefaultTextHeight(), - textAt, u, v, LineCallback, (void *)this); - } else { - if(refps) refps[0] = refps[1] = textAt; - Point2d ref = SS.GW.ProjectPoint(textAt); - dogd.dmin = min(dogd.dmin, ref.DistanceTo(dogd.mp)-10); - } + Vector ex = VectorFont::Builtin()->GetExtents(textHeight, "T"); + canvas->DrawVectorText("T", textHeight, textAt.Minus(ex.ScaledBy(0.5)), u, v, hcs); + if(refs) refs->push_back(textAt); return; } @@ -947,13 +926,13 @@ void Constraint::DrawOrGetDistance(Vector *labelPos, Vector *refps) { for(int i = 0; i < 2; i++) { Entity *e = SK.GetEntity(i == 0 ? entityA : entityB); Vector n = e->VectorGetNum(); - n = n.WithMagnitude(25/SS.GW.scale); - Vector u = (gn.Cross(n)).WithMagnitude(4/SS.GW.scale); + n = n.WithMagnitude(25/camera.scale); + Vector u = (gn.Cross(n)).WithMagnitude(4/camera.scale); Vector p = e->VectorGetRefPoint(); - LineDrawOrGetDistance(p.Plus(u), p.Plus(u).Plus(n)); - LineDrawOrGetDistance(p.Minus(u), p.Minus(u).Plus(n)); - if(refps) refps[i] = p.Plus(n.ScaledBy(0.5)); + canvas->DrawLine(p.Plus(u), p.Plus(u).Plus(n), hcs); + canvas->DrawLine(p.Minus(u), p.Minus(u).Plus(n), hcs); + if(refs) refs->push_back(p.Plus(n.ScaledBy(0.5))); } return; } @@ -961,24 +940,22 @@ void Constraint::DrawOrGetDistance(Vector *labelPos, Vector *refps) { case Type::EQUAL_RADIUS: { for(int i = 0; i < 2; i++) { Vector ref; - DoEqualRadiusTicks(i == 0 ? entityA : entityB, &ref); - if(refps) refps[i] = ref; + DoEqualRadiusTicks(canvas, hcs, i == 0 ? entityA : entityB, &ref); + if(refs) refs->push_back(ref); } return; } case Type::EQUAL_LINE_ARC_LEN: { Entity *line = SK.GetEntity(entityA); - Vector refa, refb; - DoEqualLenTicks( + Vector ref; + DoEqualLenTicks(canvas, hcs, SK.GetEntity(line->point[0])->PointGetNum(), SK.GetEntity(line->point[1])->PointGetNum(), - gn, &refa); - DoEqualRadiusTicks(entityB, &refb); // FIXME - if(refps) { - refps[0] = refa; - refps[1] = refb; - } + gn, &ref); + if(refs) refs->push_back(ref); + DoEqualRadiusTicks(canvas, hcs, entityB, &ref); + if(refs) refs->push_back(ref); return; } @@ -992,17 +969,17 @@ void Constraint::DrawOrGetDistance(Vector *labelPos, Vector *refps) { b = SK.GetEntity(e->point[1])->PointGetNum(); if(workplane.v != Entity::FREE_IN_3D.v) { - DoProjectedPoint(&a); - DoProjectedPoint(&b); + DoProjectedPoint(canvas, hcs, &a); + DoProjectedPoint(canvas, hcs, &b); } Vector ref; - DoEqualLenTicks(a, b, gn, &ref); - if(refps) refps[i] = ref; + DoEqualLenTicks(canvas, hcs, a, b, gn, &ref); + if(refs) refs->push_back(ref); } if((type == Type::LENGTH_RATIO) || (type == Type::LENGTH_DIFFERENCE)) { Vector ref = ((a.Plus(b)).ScaledBy(0.5)).Plus(disp.offset); - DoLabel(ref, labelPos, gr, gu); + DoLabel(canvas, hcs, ref, labelPos, gr, gu); } return; } @@ -1012,28 +989,28 @@ void Constraint::DrawOrGetDistance(Vector *labelPos, Vector *refps) { Vector a = SK.GetEntity(forLen->point[0])->PointGetNum(), b = SK.GetEntity(forLen->point[1])->PointGetNum(); if(workplane.v != Entity::FREE_IN_3D.v) { - DoProjectedPoint(&a); - DoProjectedPoint(&b); + DoProjectedPoint(canvas, hcs, &a); + DoProjectedPoint(canvas, hcs, &b); } Vector refa; - DoEqualLenTicks(a, b, gn, &refa); - if(refps) refps[0] = refa; + DoEqualLenTicks(canvas, hcs, a, b, gn, &refa); + if(refs) refs->push_back(refa); Entity *ln = SK.GetEntity(entityB); Vector la = SK.GetEntity(ln->point[0])->PointGetNum(), lb = SK.GetEntity(ln->point[1])->PointGetNum(); Vector pt = SK.GetEntity(ptA)->PointGetNum(); if(workplane.v != Entity::FREE_IN_3D.v) { - DoProjectedPoint(&pt); + DoProjectedPoint(canvas, hcs, &pt); la = la.ProjectInto(workplane); lb = lb.ProjectInto(workplane); } Vector closest = pt.ClosestPointOnLine(la, lb.Minus(la)); - LineDrawOrGetDistance(pt, closest); + canvas->DrawLine(pt, closest, hcs); Vector refb; - DoEqualLenTicks(pt, closest, gn, &refb); - if(refps) refps[1] = refb; + DoEqualLenTicks(canvas, hcs, pt, closest, gn, &refb); + if(refs) refs->push_back(refb); return; } @@ -1046,17 +1023,17 @@ void Constraint::DrawOrGetDistance(Vector *labelPos, Vector *refps) { Vector pt = pte->PointGetNum(); if(workplane.v != Entity::FREE_IN_3D.v) { - DoProjectedPoint(&pt); + DoProjectedPoint(canvas, hcs, &pt); la = la.ProjectInto(workplane); lb = lb.ProjectInto(workplane); } Vector closest = pt.ClosestPointOnLine(la, lb.Minus(la)); - LineDrawOrGetDistance(pt, closest); + canvas->DrawLine(pt, closest, hcs); Vector ref; - DoEqualLenTicks(pt, closest, gn, &ref); - if(refps) refps[i] = ref; + DoEqualLenTicks(canvas, hcs, pt, closest, gn, &ref); + if(refs) refs->push_back(ref); } return; } @@ -1093,14 +1070,14 @@ s: // they might not be in the same direction, even when the // constraint is fully solved. d = n.ScaledBy(d.Dot(n)); - d = d.WithMagnitude(20/SS.GW.scale); + d = d.WithMagnitude(20/camera.scale); Vector tip = tail.Plus(d); - if(refps) refps[i] = tip; - LineDrawOrGetDistance(tail, tip); - d = d.WithMagnitude(9/SS.GW.scale); - LineDrawOrGetDistance(tip, tip.Minus(d.RotatedAbout(gn, 0.6))); - LineDrawOrGetDistance(tip, tip.Minus(d.RotatedAbout(gn, -0.6))); + canvas->DrawLine(tail, tip, hcs); + d = d.WithMagnitude(9/camera.scale); + canvas->DrawLine(tip, tip.Minus(d.RotatedAbout(gn, 0.6)), hcs); + canvas->DrawLine(tip, tip.Minus(d.RotatedAbout(gn, -0.6)), hcs); + if(refs) refs->push_back(tip); } return; } @@ -1123,23 +1100,22 @@ s: Vector b = SK.GetEntity(e->point[1])->PointGetNum(); Vector m = (a.ScaledBy(0.5)).Plus(b.ScaledBy(0.5)); Vector offset = (a.Minus(b)).Cross(n); - offset = offset.WithMagnitude(13/SS.GW.scale); + offset = offset.WithMagnitude(textHeight); // Draw midpoint constraint on other side of line, so that // a line can be midpoint and horizontal at same time. if(type == Type::AT_MIDPOINT) offset = offset.ScaledBy(-1); - if(dogd.drawing) { - const char *s = (type == Type::HORIZONTAL) ? "H" : ( - (type == Type::VERTICAL) ? "V" : ( - (type == Type::AT_MIDPOINT) ? "M" : NULL)); - - ssglWriteTextRefCenter(s, Style::DefaultTextHeight(), - m.Plus(offset), r, u, LineCallback, (void *)this); - } else { - if(refps) refps[0] = refps[1] = m.Plus(offset); - Point2d ref = SS.GW.ProjectPoint(m.Plus(offset)); - dogd.dmin = min(dogd.dmin, ref.DistanceTo(dogd.mp)-10); + std::string s; + switch(type) { + case Type::HORIZONTAL: s = "H"; break; + case Type::VERTICAL: s = "V"; break; + case Type::AT_MIDPOINT: s = "M"; break; + default: ssassert(false, "Unexpected constraint type"); } + Vector o = m.Plus(offset).Plus(u.WithMagnitude(textHeight/5)), + ex = VectorFont::Builtin()->GetExtents(textHeight, s); + canvas->DrawVectorText(s, textHeight, o.Minus(ex.ScaledBy(0.5)), r, u, hcs); + if(refs) refs->push_back(o); } else { Vector a = SK.GetEntity(ptA)->PointGetNum(); Vector b = SK.GetEntity(ptB)->PointGetNum(); @@ -1157,32 +1133,22 @@ s: if(oo.Dot(d) < 0) d = d.ScaledBy(-1); Vector dp = cn.Cross(d); - d = d.WithMagnitude(14/SS.GW.scale); + d = d.WithMagnitude(14/camera.scale); Vector c = o.Minus(d); - LineDrawOrGetDistance(o, c); - d = d.WithMagnitude(3/SS.GW.scale); - dp = dp.WithMagnitude(2/SS.GW.scale); - if(dogd.drawing) { - glBegin(GL_QUADS); - ssglVertex3v((c.Plus(d)).Plus(dp)); - ssglVertex3v((c.Minus(d)).Plus(dp)); - ssglVertex3v((c.Minus(d)).Minus(dp)); - ssglVertex3v((c.Plus(d)).Minus(dp)); - glEnd(); - } else { - if(refps) refps[0] = refps[1] = c; - Point2d ref = SS.GW.ProjectPoint(c); - dogd.dmin = min(dogd.dmin, ref.DistanceTo(dogd.mp)-6); - } + canvas->DrawLine(o, c, hcs); + d = d.WithMagnitude(3/camera.scale); + dp = dp.WithMagnitude(2/camera.scale); + canvas->DrawQuad((c.Plus(d)).Plus(dp), + (c.Minus(d)).Plus(dp), + (c.Minus(d)).Minus(dp), + (c.Plus(d)).Minus(dp), + hcf); + if(refs) refs->push_back(c); } } return; case Type::COMMENT: { - if(dogd.drawing && disp.style.v) { - ssglLineWidth(Style::Width(disp.style)); - ssglColorRGB(Style::Color(disp.style)); - } Vector u, v; if(workplane.v == Entity::FREE_IN_3D.v) { u = gr; @@ -1192,59 +1158,40 @@ s: u = norm->NormalU(); v = norm->NormalV(); } - if(refps) refps[0] = refps[1] = disp.offset; - DoLabel(disp.offset, labelPos, u, v); + + if(disp.style.v != 0) { + stroke.width = Style::Width(disp.style); + if(how == DrawAs::DEFAULT) { + stroke.color = Style::Color(disp.style); + } + hcs = canvas->GetStroke(stroke); + } + DoLabel(canvas, hcs, disp.offset, labelPos, u, v); + if(refs) refs->push_back(disp.offset); return; } } ssassert(false, "Unexpected constraint type"); } -void Constraint::Draw() { - dogd.drawing = true; - dogd.sel = NULL; - hStyle hs = GetStyle(); - - ssglLineWidth(Style::Width(hs)); - ssglColorRGB(Style::Color(hs)); - - DrawOrGetDistance(NULL, NULL); +void Constraint::Draw(DrawAs how, Canvas *canvas) { + DoLayout(how, canvas, NULL, NULL); } -double Constraint::GetDistance(Point2d mp) { - dogd.drawing = false; - dogd.sel = NULL; - dogd.mp = mp; - dogd.dmin = 1e12; - - DrawOrGetDistance(NULL, NULL); - - return dogd.dmin; -} - -Vector Constraint::GetLabelPos() { - dogd.drawing = false; - dogd.sel = NULL; - dogd.mp.x = 0; dogd.mp.y = 0; - dogd.dmin = 1e12; +Vector Constraint::GetLabelPos(const Camera &camera) { + ObjectPicker canvas = {}; + canvas.camera = camera; Vector p; - DrawOrGetDistance(&p, NULL); + DoLayout(DrawAs::DEFAULT, &canvas, &p, NULL); return p; } -void Constraint::GetReferencePos(Vector *refps) { - dogd.drawing = false; - dogd.sel = NULL; +void Constraint::GetReferencePoints(const Camera &camera, std::vector *refs) { + ObjectPicker canvas = {}; + canvas.camera = camera; - DrawOrGetDistance(NULL, refps); -} - -void Constraint::GetEdges(SEdgeList *sel) { - dogd.drawing = true; - dogd.sel = sel; - DrawOrGetDistance(NULL, NULL); - dogd.sel = NULL; + DoLayout(DrawAs::DEFAULT, &canvas, NULL, refs); } bool Constraint::IsStylable() const { diff --git a/src/drawentity.cpp b/src/drawentity.cpp index b5e1dba2..8ccd9230 100644 --- a/src/drawentity.cpp +++ b/src/drawentity.cpp @@ -17,109 +17,6 @@ std::string Entity::DescriptionString() const { } } -void Entity::LineDrawOrGetDistance(Vector a, Vector b, bool maybeFat, int data) { - if(dogd.drawing) { - // Draw lines from active group in front of those from previous - ssglDepthRangeOffset((group.v == SS.GW.activeGroup.v) ? 4 : 3); - // Narrow lines are drawn as lines, but fat lines must be drawn as - // filled polygons, to get the line join style right. - ssglStippledLine(a, b, dogd.lineWidth, dogd.stippleType, dogd.stippleScale, maybeFat); - ssglDepthRangeOffset(0); - } else { - Point2d ap = SS.GW.ProjectPoint(a); - Point2d bp = SS.GW.ProjectPoint(b); - - double d = dogd.mp.DistanceToLine(ap, bp.Minus(ap), /*asSegment=*/true); - // A little bit easier to select in the active group - if(group.v == SS.GW.activeGroup.v) d -= 1; - if(d < dogd.dmin) { - dogd.dmin = d; - dogd.data = data; - } - } -} - -void Entity::DrawAll(bool drawAsHidden) { - // This handles points as a special case, because I seem to be able - // to get a huge speedup that way, by consolidating stuff to gl. - int i; - if(SS.GW.showPoints) { - double s = 3.5/SS.GW.scale; - Vector r = SS.GW.projRight.ScaledBy(s); - Vector d = SS.GW.projUp.ScaledBy(s); - ssglColorRGB(Style::Color(Style::DATUM)); - ssglDepthRangeOffset(6); - glBegin(GL_QUADS); - for(i = 0; i < SK.entity.n; i++) { - Entity *e = &(SK.entity.elem[i]); - if(!e->IsPoint()) continue; - if(!(SK.GetGroup(e->group)->IsVisible())) continue; - if(e->forceHidden) continue; - - Vector v = e->PointGetNum(); - - // If we're analyzing the sketch to show the degrees of freedom, - // then we draw big colored squares over the points that are - // free to move. - bool free = false; - if(e->type == Type::POINT_IN_3D) { - Param *px = SK.GetParam(e->param[0]), - *py = SK.GetParam(e->param[1]), - *pz = SK.GetParam(e->param[2]); - - free = (px->free) || (py->free) || (pz->free); - } else if(e->type == Type::POINT_IN_2D) { - Param *pu = SK.GetParam(e->param[0]), - *pv = SK.GetParam(e->param[1]); - - free = (pu->free) || (pv->free); - } - if(free) { - Vector re = r.ScaledBy(2.5), de = d.ScaledBy(2.5); - - ssglColorRGB(Style::Color(Style::ANALYZE)); - ssglVertex3v(v.Plus (re).Plus (de)); - ssglVertex3v(v.Plus (re).Minus(de)); - ssglVertex3v(v.Minus(re).Minus(de)); - ssglVertex3v(v.Minus(re).Plus (de)); - ssglColorRGB(Style::Color(Style::DATUM)); - } - - ssglVertex3v(v.Plus (r).Plus (d)); - ssglVertex3v(v.Plus (r).Minus(d)); - ssglVertex3v(v.Minus(r).Minus(d)); - ssglVertex3v(v.Minus(r).Plus (d)); - } - glEnd(); - ssglDepthRangeOffset(0); - } - - for(i = 0; i < SK.entity.n; i++) { - Entity *e = &(SK.entity.elem[i]); - if(e->IsPoint()) { - continue; // already handled - } - e->Draw(drawAsHidden); - } -} - -void Entity::Draw(bool drawAsHidden) { - hStyle hs = Style::ForEntity(h); - dogd.lineWidth = Style::Width(hs); - if(drawAsHidden) { - dogd.stippleType = Style::PatternType({ Style::HIDDEN_EDGE }); - dogd.stippleScale = Style::StippleScaleMm({ Style::HIDDEN_EDGE }); - } else { - dogd.stippleType = Style::PatternType(hs); - dogd.stippleScale = Style::StippleScaleMm(hs); - } - ssglLineWidth((float)dogd.lineWidth); - ssglColorRGB(Style::Color(hs)); - - dogd.drawing = true; - DrawOrGetDistance(); -} - void Entity::GenerateEdges(SEdgeList *el) { SBezierList *sbl = GetOrGenerateBezierCurves(); @@ -185,17 +82,7 @@ BBox Entity::GetOrGenerateScreenBBox(bool *hasBBox) { return screenBBox; } -double Entity::GetDistance(Point2d mp) { - dogd.drawing = false; - dogd.mp = mp; - dogd.dmin = 1e12; - - DrawOrGetDistance(); - - return dogd.dmin; -} - -Vector Entity::GetReferencePos() { +void Entity::GetReferencePoints(std::vector *refs) { switch(type) { case Type::POINT_N_COPY: case Type::POINT_N_TRANS: @@ -203,7 +90,8 @@ Vector Entity::GetReferencePos() { case Type::POINT_N_ROT_AA: case Type::POINT_IN_3D: case Type::POINT_IN_2D: - return PointGetNum(); + refs->push_back(PointGetNum()); + break; case Type::NORMAL_N_COPY: case Type::NORMAL_N_ROT: @@ -216,12 +104,14 @@ Vector Entity::GetReferencePos() { case Type::CUBIC: case Type::CUBIC_PERIODIC: case Type::TTF_TEXT: - return SK.GetEntity(point[0])->PointGetNum(); + refs->push_back(SK.GetEntity(point[0])->PointGetNum()); + break; case Type::LINE_SEGMENT: { Vector a = SK.GetEntity(point[0])->PointGetNum(), b = SK.GetEntity(point[1])->PointGetNum(); - return b.Plus(a.Minus(b).ScaledBy(0.5)); + refs->push_back(b.Plus(a.Minus(b).ScaledBy(0.5))); + break; } case Type::DISTANCE: @@ -233,7 +123,17 @@ Vector Entity::GetReferencePos() { case Type::FACE_N_ROT_AA: break; } - ssassert(false, "Unexpected entity type"); +} + +int Entity::GetPositionOfPoint(const Camera &camera, Point2d p) { + ObjectPicker canvas = {}; + canvas.camera = camera; + canvas.point = p; + canvas.minDistance = 1e12; + + Draw(DrawAs::DEFAULT, &canvas); + + return canvas.position; } bool Entity::IsStylable() const { @@ -252,8 +152,7 @@ bool Entity::IsVisible() const { } if(!(g->IsVisible())) return false; - // Don't check if points are hidden; this gets called only for - // selected or hovered points, and those should always be shown. + if(IsPoint() && !SS.GW.showPoints) return false; if(IsNormal() && !SS.GW.showNormals) return false; if(!SS.GW.showWorkplanes) { @@ -296,10 +195,6 @@ void Entity::CalculateNumerical(bool forExport) { } } -bool Entity::PointIsFromReferences() const { - return h.request().IsFromReferences(); -} - //----------------------------------------------------------------------------- // Compute a cubic, second derivative continuous, interpolating spline. Same // routine for periodic splines (in a loop) or open splines (with specified @@ -544,17 +439,61 @@ void Entity::GenerateBezierCurves(SBezierList *sbl) const { } } -void Entity::DrawOrGetDistance() { - // If we're about to perform hit testing on an entity, consider - // whether the pointer is inside its bounding box first. - if(!dogd.drawing && !IsNormal()) { - bool hasBBox; - BBox box = GetOrGenerateScreenBBox(&hasBBox); - if(hasBBox && !box.Contains(dogd.mp, SELECTION_RADIUS)) - return; +void Entity::Draw(DrawAs how, Canvas *canvas) { + if(!IsVisible()) return; + + int zIndex; + if(how == DrawAs::HIDDEN) { + zIndex = 2; + } else if(group.v != SS.GW.activeGroup.v) { + zIndex = 3; + } else { + zIndex = 4; } - if(!IsVisible()) return; + hStyle hs; + if(IsPoint()) { + hs.v = Style::DATUM; + } else if(IsNormal() || type == Type::WORKPLANE) { + hs.v = Style::NORMALS; + } else { + hs = Style::ForEntity(h); + } + + Canvas::Stroke stroke = {}; + stroke.zIndex = zIndex; + stroke.color = Style::Color(hs); + stroke.width = Style::Width(hs); + stroke.stipplePattern = Style::PatternType(hs); + stroke.stippleScale = Style::StippleScaleMm(hs); + switch(how) { + case DrawAs::DEFAULT: + stroke.layer = Canvas::Layer::NORMAL; + break; + + case DrawAs::HIDDEN: + stroke.layer = Canvas::Layer::OCCLUDED; + stroke.stipplePattern = Style::PatternType({ Style::HIDDEN_EDGE }); + stroke.stippleScale = Style::StippleScaleMm({ Style::HIDDEN_EDGE }); + break; + + case DrawAs::HOVERED: + stroke.layer = Canvas::Layer::FRONT; + stroke.color = Style::Color(Style::HOVERED); + break; + + case DrawAs::SELECTED: + stroke.layer = Canvas::Layer::FRONT; + stroke.color = Style::Color(Style::SELECTED); + break; + } + Canvas::hStroke hcs = canvas->GetStroke(stroke); + + Canvas::Fill fill = {}; + fill.layer = stroke.layer; + fill.zIndex = IsPoint() ? zIndex + 1 : 0; + fill.color = stroke.color; + Canvas::hFill hcf = canvas->GetFill(fill); switch(type) { case Type::POINT_N_COPY: @@ -563,26 +502,33 @@ void Entity::DrawOrGetDistance() { case Type::POINT_N_ROT_AA: case Type::POINT_IN_3D: case Type::POINT_IN_2D: { - Vector v = PointGetNum(); + if(how == DrawAs::HIDDEN) return; - if(dogd.drawing) { - double s = 3.5; - Vector r = SS.GW.projRight.ScaledBy(s/SS.GW.scale); - Vector d = SS.GW.projUp.ScaledBy(s/SS.GW.scale); + // If we're analyzing the sketch to show the degrees of freedom, + // then we draw big colored squares over the points that are + // free to move. + bool free = false; + if(type == Type::POINT_IN_3D) { + Param *px = SK.GetParam(param[0]), + *py = SK.GetParam(param[1]), + *pz = SK.GetParam(param[2]); - ssglColorRGB(Style::Color(Style::DATUM)); - ssglDepthRangeOffset(6); - glBegin(GL_QUADS); - ssglVertex3v(v.Plus (r).Plus (d)); - ssglVertex3v(v.Plus (r).Minus(d)); - ssglVertex3v(v.Minus(r).Minus(d)); - ssglVertex3v(v.Minus(r).Plus (d)); - glEnd(); - ssglDepthRangeOffset(0); - } else { - Point2d pp = SS.GW.ProjectPoint(v); - dogd.dmin = pp.DistanceTo(dogd.mp) - 6; + free = px->free || py->free || pz->free; + } else if(type == Type::POINT_IN_2D) { + Param *pu = SK.GetParam(param[0]), + *pv = SK.GetParam(param[1]); + + free = pu->free || pv->free; } + if(free) { + Canvas::Fill fillAnalyze = fill; + fillAnalyze.color = Style::Color(Style::ANALYZE); + Canvas::hFill hcfAnalyze = canvas->GetFill(fillAnalyze); + + canvas->DrawPoint(PointGetNum(), 7.0, hcfAnalyze); + } + + canvas->DrawPoint(PointGetNum(), 3.5, hcf); return; } @@ -591,67 +537,59 @@ void Entity::DrawOrGetDistance() { case Type::NORMAL_N_ROT_AA: case Type::NORMAL_IN_3D: case Type::NORMAL_IN_2D: { - int i; - for(i = 0; i < 2; i++) { - if(i == 0 && !SS.GW.showNormals) { - // When the normals are hidden, we will continue to show - // the coordinate axes at the bottom left corner, but - // not at the origin. - continue; + const Camera &camera = canvas->GetCamera(); + + if(how == DrawAs::HIDDEN) return; + + for(int i = 0; i < 2; i++) { + bool asReference = (i == 1); + if(asReference) { + if(!h.request().IsFromReferences()) continue; + } else { + if(!SK.GetGroup(group)->IsVisible() || !SS.GW.showNormals) continue; } - hRequest hr = h.request(); - // Always draw the x, y, and z axes in red, green, and blue; - // brighter for the ones at the bottom left of the screen, - // dimmer for the ones at the model origin. - int f = (i == 0 ? 100 : 255); - if(hr.v == Request::HREQUEST_REFERENCE_XY.v) { - if(dogd.drawing) - ssglColorRGB(RGBi(0, 0, f)); - } else if(hr.v == Request::HREQUEST_REFERENCE_YZ.v) { - if(dogd.drawing) - ssglColorRGB(RGBi(f, 0, 0)); - } else if(hr.v == Request::HREQUEST_REFERENCE_ZX.v) { - if(dogd.drawing) - ssglColorRGB(RGBi(0, f, 0)); - } else { - if(dogd.drawing) - ssglColorRGB(Style::Color(Style::NORMALS)); - if(i > 0) break; + stroke.layer = (asReference) ? Canvas::Layer::FRONT : Canvas::Layer::NORMAL; + if(how == DrawAs::DEFAULT) { + // Always draw the x, y, and z axes in red, green, and blue; + // brighter for the ones at the bottom left of the screen, + // dimmer for the ones at the model origin. + hRequest hr = h.request(); + uint8_t luma = (asReference) ? 255 : 100; + if(hr.v == Request::HREQUEST_REFERENCE_XY.v) { + stroke.color = RgbaColor::From(0, 0, luma); + } else if(hr.v == Request::HREQUEST_REFERENCE_YZ.v) { + stroke.color = RgbaColor::From(luma, 0, 0); + } else if(hr.v == Request::HREQUEST_REFERENCE_ZX.v) { + stroke.color = RgbaColor::From(0, luma, 0); + } } + hcs = canvas->GetStroke(stroke); Quaternion q = NormalGetNum(); Vector tail; - if(i == 0) { - tail = SK.GetEntity(point[0])->PointGetNum(); - if(dogd.drawing) - ssglLineWidth(1); - } else { + if(asReference) { // Draw an extra copy of the x, y, and z axes, that's // always in the corner of the view and at the front. // So those are always available, perhaps useful. - double s = SS.GW.scale; - double h = 60 - SS.GW.height/2; - double w = 60 - SS.GW.width/2; - tail = SS.GW.projRight.ScaledBy(w/s).Plus( - SS.GW.projUp. ScaledBy(h/s)).Minus(SS.GW.offset); - if(dogd.drawing) { - ssglDepthRangeLockToFront(true); - ssglLineWidth(2); - } + stroke.width = 2; + double s = camera.scale; + double h = 60 - camera.height / 2.0; + double w = 60 - camera.width / 2.0; + tail = camera.projRight.ScaledBy(w/s).Plus( + camera.projUp. ScaledBy(h/s)).Minus(camera.offset); + } else { + tail = SK.GetEntity(point[0])->PointGetNum(); } - - Vector v = (q.RotationN()).WithMagnitude(50/SS.GW.scale); + Vector v = (q.RotationN()).WithMagnitude(50.0 / camera.scale); Vector tip = tail.Plus(v); - LineDrawOrGetDistance(tail, tip); + canvas->DrawLine(tail, tip, hcs); - v = v.WithMagnitude(12/SS.GW.scale); + v = v.WithMagnitude(12.0 / camera.scale); Vector axis = q.RotationV(); - LineDrawOrGetDistance(tip,tip.Minus(v.RotatedAbout(axis, 0.6))); - LineDrawOrGetDistance(tip,tip.Minus(v.RotatedAbout(axis,-0.6))); + canvas->DrawLine(tip, tip.Minus(v.RotatedAbout(axis, 0.6)), hcs); + canvas->DrawLine(tip, tip.Minus(v.RotatedAbout(axis, -0.6)), hcs); } - if(dogd.drawing) - ssglDepthRangeLockToFront(false); return; } @@ -661,13 +599,14 @@ void Entity::DrawOrGetDistance() { return; case Type::WORKPLANE: { - Vector p; - p = SK.GetEntity(point[0])->PointGetNum(); + const Camera &camera = canvas->GetCamera(); + + Vector p = SK.GetEntity(point[0])->PointGetNum(); Vector u = Normal()->NormalU(); Vector v = Normal()->NormalV(); - double s = (min(SS.GW.width, SS.GW.height))*0.45/SS.GW.scale; + double s = (std::min(camera.width, camera.height)) * 0.45 / camera.scale; Vector us = u.ScaledBy(s); Vector vs = v.ScaledBy(s); @@ -677,41 +616,28 @@ void Entity::DrawOrGetDistance() { Vector mm = p.Minus(us).Minus(vs), mm2 = mm; Vector mp = p.Minus(us).Plus (vs); - if(dogd.drawing) { - ssglLineWidth(1); - ssglColorRGB(Style::Color(Style::NORMALS)); - glEnable(GL_LINE_STIPPLE); - glLineStipple(3, 0x1111); - } + Canvas::Stroke strokeBorder = stroke; + strokeBorder.zIndex -= 3; + strokeBorder.stipplePattern = StipplePattern::SHORT_DASH; + strokeBorder.stippleScale = 8.0 / camera.scale; + Canvas::hStroke hcsBorder = canvas->GetStroke(strokeBorder); + + double textHeight = Style::TextHeight(hs) / camera.scale; if(!h.isFromRequest()) { - mm = mm.Plus(v.ScaledBy(70/SS.GW.scale)); - mm2 = mm2.Plus(u.ScaledBy(70/SS.GW.scale)); - LineDrawOrGetDistance(mm2, mm); + mm = mm.Plus(v.ScaledBy(textHeight * 4.7)); + mm2 = mm2.Plus(u.ScaledBy(textHeight * 4.7)); + canvas->DrawLine(mm2, mm, hcsBorder); } - LineDrawOrGetDistance(pp, pm); - LineDrawOrGetDistance(pm, mm2); - LineDrawOrGetDistance(mp, mm); - LineDrawOrGetDistance(pp, mp); + canvas->DrawLine(pp, pm, hcsBorder); + canvas->DrawLine(mm2, pm, hcsBorder); + canvas->DrawLine(mm, mp, hcsBorder); + canvas->DrawLine(pp, mp, hcsBorder); - if(dogd.drawing) - glDisable(GL_LINE_STIPPLE); - - std::string str = DescriptionString().substr(5); - double th = Style::DefaultTextHeight(); - if(dogd.drawing) { - Vector o = mm2.Plus(u.ScaledBy(3/SS.GW.scale)).Plus( - v.ScaledBy(3/SS.GW.scale)); - ssglWriteText(str, th, o, u, v, NULL, NULL); - } else { - Vector pos = mm2.Plus(u.ScaledBy(ssglStrWidth(str, th)/2)).Plus( - v.ScaledBy(ssglStrCapHeight(th)/2)); - Point2d pp = SS.GW.ProjectPoint(pos); - dogd.dmin = min(dogd.dmin, pp.DistanceTo(dogd.mp) - 10); - // If a line lies in a plane, then select the line, not - // the plane. - dogd.dmin += 3; - } + Vector o = mm2.Plus(u.ScaledBy(3.0 / camera.scale)).Plus( + v.ScaledBy(3.0 / camera.scale)); + std::string shortDesc = DescriptionString().substr(5); + canvas->DrawVectorText(shortDesc, textHeight, o, u, v, hcs); return; } @@ -721,13 +647,10 @@ void Entity::DrawOrGetDistance() { case Type::CUBIC: case Type::CUBIC_PERIODIC: case Type::TTF_TEXT: { - // Nothing but the curves; generate the rational polynomial curves for - // everything, then piecewise linearize them, and display those. - SEdgeList *sel = GetOrGenerateEdges(); - dogd.data = -1; - for(int i = 0; i < sel->l.n; i++) { - SEdge *se = &(sel->l.elem[i]); - LineDrawOrGetDistance(se->a, se->b, /*maybeFat=*/true, se->auxB); + // Generate the rational polynomial curves, then piecewise linearize + // them, and display those. + if(!canvas->DrawBeziers(*GetOrGenerateBezierCurves(), hcs)) { + canvas->DrawEdges(*GetOrGenerateEdges(), hcs); } return; } @@ -742,4 +665,3 @@ void Entity::DrawOrGetDistance() { } ssassert(false, "Unexpected entity type"); } - diff --git a/src/dsc.h b/src/dsc.h index 49e2a60c..d8d76fbc 100644 --- a/src/dsc.h +++ b/src/dsc.h @@ -135,6 +135,7 @@ public: double Dot(Point2d p) const; double DistanceTo(const Point2d &p) const; double DistanceToLine(const Point2d &p0, const Point2d &dp, bool asSegment) const; + double DistanceToLineSigned(const Point2d &p0, const Point2d &dp, bool asSegment) const; double Angle() const; double AngleTo(const Point2d &p) const; double Magnitude() const; @@ -459,6 +460,8 @@ public: float blueF() const { return (float)blue / 255.0f; } float alphaF() const { return (float)alpha / 255.0f; } + bool IsEmpty() const { return alpha == 0; } + bool Equals(RgbaColor c) const { return c.red == red && @@ -467,6 +470,12 @@ public: c.alpha == alpha; } + RgbaColor WithAlpha(uint8_t newAlpha) const { + RgbaColor color = *this; + color.alpha = newAlpha; + return color; + } + uint32_t ToPackedIntBGRA() const { return blue | diff --git a/src/export.cpp b/src/export.cpp index dd7df394..3739a6ef 100644 --- a/src/export.cpp +++ b/src/export.cpp @@ -10,7 +10,6 @@ #ifndef WIN32 #include #endif -#include void SolveSpaceUI::ExportSectionTo(const std::string &filename) { Vector gn = (SS.GW.projRight).Cross(SS.GW.projUp); @@ -110,6 +109,66 @@ void SolveSpaceUI::ExportSectionTo(const std::string &filename) { bl.Clear(); } +// This is an awful temporary hack to replace Constraint::GetEdges until we have proper +// export through Canvas. +class GetEdgesCanvas : public Canvas { +public: + Camera camera; + SEdgeList *edges; + + const Camera &GetCamera() const override { + return camera; + } + + void DrawLine(const Vector &a, const Vector &b, hStroke hcs) override { + edges->AddEdge(a, b, Style::CONSTRAINT); + } + void DrawEdges(const SEdgeList &el, hStroke hcs) override { + for(const SEdge &e : el.l) { + edges->AddEdge(e.a, e.b, Style::CONSTRAINT); + } + } + void DrawVectorText(const std::string &text, double height, + const Vector &o, const Vector &u, const Vector &v, + hStroke hcs) override { + auto traceEdge = [&](Vector a, Vector b) { edges->AddEdge(a, b, Style::CONSTRAINT); }; + VectorFont::Builtin()->Trace(height, o, u, v, text, traceEdge, camera); + } + + void DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d, + hFill hcf) override { + // Do nothing + } + + bool DrawBeziers(const SBezierList &bl, hStroke hcs) override { + ssassert(false, "Not implemented"); + } + void DrawOutlines(const SOutlineList &ol, hStroke hcs) override { + ssassert(false, "Not implemented"); + } + void DrawPoint(const Vector &o, double d, hFill hcf) override { + ssassert(false, "Not implemented"); + } + void DrawPolygon(const SPolygon &p, hFill hcf) override { + ssassert(false, "Not implemented"); + } + void DrawMesh(const SMesh &m, hFill hcfFront, hFill hcfBack = {}, + hStroke hcsTriangles = {}) override { + ssassert(false, "Not implemented"); + } + void DrawFaces(const SMesh &m, const std::vector &faces, hFill hcf) override { + ssassert(false, "Not implemented"); + } + void DrawPixmap(std::shared_ptr pm, + const Vector &o, const Vector &u, const Vector &v, + const Point2d &ta, const Point2d &tb, hFill hcf) override { + ssassert(false, "Not implemented"); + } + void InvalidatePixmap(std::shared_ptr pm) override { + ssassert(false, "Not implemented"); + } +}; + void SolveSpaceUI::ExportViewOrWireframeTo(const std::string &filename, bool exportWireframe) { int i; SEdgeList edges = {}; @@ -159,11 +218,14 @@ void SolveSpaceUI::ExportViewOrWireframeTo(const std::string &filename, bool exp if(SS.GW.showConstraints) { if(!out->OutputConstraints(&SK.constraint)) { + GetEdgesCanvas canvas = {}; + canvas.camera = SS.GW.GetCamera(); + canvas.edges = &edges; + // The output format cannot represent constraints directly, // so convert them to edges. - Constraint *c; - for(c = SK.constraint.First(); c; c = SK.constraint.NextAfter(c)) { - c->GetEdges(&edges); + for(Constraint &c : SK.constraint) { + c.Draw(Constraint::DrawAs::DEFAULT, &canvas); } } } @@ -1021,62 +1083,26 @@ void SolveSpaceUI::ExportMeshAsThreeJsTo(FILE *f, const std::string &filename, // rendering the view in the usual way and then copying the pixels. //----------------------------------------------------------------------------- void SolveSpaceUI::ExportAsPngTo(const std::string &filename) { - int w = (int)SS.GW.width, h = (int)SS.GW.height; // No guarantee that the back buffer contains anything valid right now, // so repaint the scene. And hide the toolbar too. bool prevShowToolbar = SS.showToolbar; SS.showToolbar = false; #ifndef WIN32 std::unique_ptr gloffscreen(new GLOffscreen); - gloffscreen->begin(w, h); + gloffscreen->begin((int)SS.GW.width, (int)SS.GW.height); #endif SS.GW.Paint(); SS.showToolbar = prevShowToolbar; + // Somewhat hacky way to invoke glReadPixels without dragging in all OpenGL headers. + OpenGl1Renderer canvas = {}; + canvas.camera = SS.GW.GetCamera(); + std::shared_ptr screenshot = canvas.ReadFrame(); + FILE *f = ssfopen(filename, "wb"); - if(!f) goto err; - - png_struct *png_ptr; png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, - NULL, NULL, NULL); - if(!png_ptr) goto err; - - png_info *info_ptr; info_ptr = png_create_info_struct(png_ptr); - if(!png_ptr) goto err; - - if(setjmp(png_jmpbuf(png_ptr))) goto err; - - png_init_io(png_ptr, f); - - // glReadPixels wants to align things on 4-boundaries, and there's 3 - // bytes per pixel. As long as the row width is divisible by 4, all - // works out. - w &= ~3; h &= ~3; - - png_set_IHDR(png_ptr, info_ptr, w, h, - 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, - PNG_COMPRESSION_TYPE_DEFAULT,PNG_FILTER_TYPE_DEFAULT); - - png_write_info(png_ptr, info_ptr); - - // Get the pixel data from the framebuffer - uint8_t *pixels; pixels = (uint8_t *)AllocTemporary(3*w*h); - uint8_t **rowptrs; rowptrs = (uint8_t **)AllocTemporary(h*sizeof(uint8_t *)); - glReadPixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, pixels); - - int y; - for(y = 0; y < h; y++) { - // gl puts the origin at lower left, but png puts it top left - rowptrs[y] = pixels + ((h - 1) - y)*(3*w); + if(!f || !screenshot->WritePng(f, /*flip=*/FLIP_FRAMEBUFFER)) { + Error("Couldn't write to '%s'", filename.c_str()); } - png_write_image(png_ptr, rowptrs); - - png_write_end(png_ptr, info_ptr); - png_destroy_write_struct(&png_ptr, &info_ptr); - fclose(f); - return; - -err: - Error("Error writing PNG file '%s'", filename.c_str()); if(f) fclose(f); return; } diff --git a/src/file.cpp b/src/file.cpp index d9545157..05f1d92c 100644 --- a/src/file.cpp +++ b/src/file.cpp @@ -684,21 +684,6 @@ static std::string Join(const std::vector &parts, const std::string return result; } -static bool PlatformPathEqual(const std::string &a, const std::string &b) -{ - // Case-sensitivity is actually per-volume on both Windows and OS X, - // but it is extremely tedious to implement and test for little benefit. -#if defined(WIN32) - std::wstring wa = Widen(a), wb = Widen(b); - return std::equal(wa.begin(), wa.end(), wb.begin(), /*wb.end(),*/ - [](wchar_t wca, wchar_t wcb) { return towlower(wca) == towlower(wcb); }); -#elif defined(__APPLE__) - return !strcasecmp(a.c_str(), b.c_str()); -#else - return a == b; -#endif -} - static std::string MakePathRelative(const std::string &base, const std::string &path) { std::vector baseParts = Split(base, PATH_SEP), @@ -708,7 +693,7 @@ static std::string MakePathRelative(const std::string &base, const std::string & size_t common; for(common = 0; common < baseParts.size() && common < pathParts.size(); common++) { - if(!PlatformPathEqual(baseParts[common], pathParts[common])) + if(!PathEqual(baseParts[common], pathParts[common])) break; } diff --git a/src/glhelper.cpp b/src/glhelper.cpp deleted file mode 100644 index 979b2480..00000000 --- a/src/glhelper.cpp +++ /dev/null @@ -1,835 +0,0 @@ -//----------------------------------------------------------------------------- -// 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, - StipplePattern stippleType, double stippleScale, bool maybeFat) -{ - const char *stipplePattern; - switch(stippleType) { - case StipplePattern::CONTINUOUS: ssglLine(a, b, width, maybeFat); return; - case StipplePattern::SHORT_DASH: stipplePattern = "- "; break; - case StipplePattern::DASH: stipplePattern = "- "; break; - case StipplePattern::LONG_DASH: stipplePattern = "_ "; break; - case StipplePattern::DASH_DOT: stipplePattern = "-."; break; - case StipplePattern::DASH_DOT_DOT: stipplePattern = "-.."; break; - case StipplePattern::DOT: stipplePattern = "."; break; - case StipplePattern::FREEHAND: stipplePattern = "~"; break; - case StipplePattern::ZIGZAG: stipplePattern = "~__"; break; - } - 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 forSelection) -{ - 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(forSelection) { - glPolygonStipple(SelMask); - } else { - glPolygonStipple(HoverMask); - } -} - -static void StippleTriangle(STriangle *tr, bool forSelection, RgbaColor rgb) -{ - glEnd(); - glDisable(GL_LIGHTING); - ssglColorRGB(rgb); - Stipple(forSelection); - 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, /*forSelection=*/true, rgbSelected); - } - if(h != 0 && tr->meta.face == h) { - StippleTriangle(tr, /*forSelection=*/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); - StipplePattern 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); - StipplePattern 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, Vector a, Vector b, Vector c, Vector d) { - 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, 0.0); - glVertex3d(CO(a)); - glTexCoord2d(0.0, 1.0); - glVertex3d(CO(b)); - glTexCoord2d(1.0, 1.0); - glVertex3d(CO(c)); - glTexCoord2d(1.0, 0.0); - glVertex3d(CO(d)); - glEnd(); - glDisable(GL_TEXTURE_2D); -} - -void ssglDrawPixmap(const Pixmap &pixmap, Point2d o, bool flip) { - double w = (double)pixmap.width, h = (double)pixmap.height; - if(!flip) { - Vector a = { o.x, o.y, 0.0 }, - b = { o.x, o.y + h, 0.0 }, - c = { o.x + w, o.y + h, 0.0 }, - d = { o.x + w, o.y, 0.0 }; - ssglDrawPixmap(pixmap, a, b, c, d); - } else { - Vector a = { o.x, o.y + h, 0.0 }, - b = { o.x, o.y, 0.0 }, - c = { o.x + w, o.y, 0.0 }, - d = { o.x + w, o.y + h, 0.0 }; - ssglDrawPixmap(pixmap, a, b, c, d); - } -} - -//----------------------------------------------------------------------------- -// 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, /*hasAlpha=*/false, std::vector(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); -} - -}; diff --git a/src/graphicswin.cpp b/src/graphicswin.cpp index 9f84fd14..cb542c85 100644 --- a/src/graphicswin.cpp +++ b/src/graphicswin.cpp @@ -910,9 +910,9 @@ void GraphicsWindow::MenuEdit(Command id) { SS.MarkGroupDirty(ep->group); } else if(s->constraint.v) { Constraint *c = SK.GetConstraint(s->constraint); - Vector refp[2]; - c->GetReferencePos(refp); - c->disp.offset = c->disp.offset.Plus(SS.GW.SnapToGrid(refp[0]).Minus(refp[0])); + std::vector refs; + c->GetReferencePoints(SS.GW.GetCamera(), &refs); + c->disp.offset = c->disp.offset.Plus(SS.GW.SnapToGrid(refs[0]).Minus(refs[0])); } } // Regenerate, with these points marked as dragged so that they diff --git a/src/groupmesh.cpp b/src/groupmesh.cpp index d85c67aa..f166733c 100644 --- a/src/groupmesh.cpp +++ b/src/groupmesh.cpp @@ -452,83 +452,137 @@ bool Group::IsMeshGroup() { } } -void Group::DrawDisplayItems(Group::Type t) { - RgbaColor specColor; - bool useSpecColor; - if(t == Type::DRAWING_3D || t == Type::DRAWING_WORKPLANE) { - // force the color to something dim - specColor = Style::Color(Style::DIM_SOLID); - useSpecColor = true; - } else { - useSpecColor = false; // use the model color - } - // The back faces are drawn in red; should never seem them, since we - // draw closed shells, so that's a debugging aid. - GLfloat mpb[] = { 1.0f, 0.1f, 0.1f, 1.0f }; - glMaterialfv(GL_BACK, GL_AMBIENT_AND_DIFFUSE, mpb); +void Group::DrawMesh(DrawMeshAs how, Canvas *canvas) { + if(!(SS.GW.showShaded || SS.GW.showHdnLines)) return; - // When we fill the mesh, we need to know which triangles are selected - // or hovered, in order to draw them differently. - uint32_t mh = 0, ms1 = 0, ms2 = 0; - hEntity he = SS.GW.hover.entity; - if(he.v != 0 && SK.GetEntity(he)->IsFace()) { - mh = he.v; - } - SS.GW.GroupSelection(); - if(gs.faces > 0) ms1 = gs.face[0].v; - if(gs.faces > 1) ms2 = gs.face[1].v; + switch(how) { + case DrawMeshAs::DEFAULT: { + // Force the shade color to something dim to not distract from + // the sketch. + Canvas::Fill fillFront = {}; + if(!SS.GW.showShaded) { + fillFront.layer = Canvas::Layer::DEPTH_ONLY; + } + if(type == Type::DRAWING_3D || type == Type::DRAWING_WORKPLANE) { + fillFront.color = Style::Color(Style::DIM_SOLID); + } + Canvas::hFill hcfFront = canvas->GetFill(fillFront); - if(SS.GW.showShaded || SS.GW.showHdnLines) { - if(SS.drawBackFaces && !displayMesh.isTransparent) { - // For debugging, draw the backs of the triangles in red, so that we - // notice when a shell is open - glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 1); - } else { - glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 0); + // The back faces are drawn in red; should never seem them, since we + // draw closed shells, so that's a debugging aid. + Canvas::hFill hcfBack = {}; + if(SS.drawBackFaces && !displayMesh.isTransparent) { + Canvas::Fill fillBack = {}; + fillBack.layer = fillFront.layer; + fillBack.color = RgbaColor::FromFloat(1.0f, 0.1f, 0.1f); + hcfBack = canvas->GetFill(fillBack); + } + + // Draw mesh edges, for debugging. + Canvas::hStroke hcsTriangle = {}; + if(SS.GW.showMesh) { + Canvas::Stroke strokeTriangle = {}; + strokeTriangle.zIndex = 1; + strokeTriangle.color = RgbaColor::FromFloat(0.0f, 1.0f, 0.0f); + strokeTriangle.width = 1; + hcsTriangle = canvas->GetStroke(strokeTriangle); + } + + // Draw the shaded solid into the depth buffer for hidden line removal, + // and if we're actually going to display it, to the color buffer too. + canvas->DrawMesh(displayMesh, hcfFront, hcfBack, hcsTriangle); + break; } - // Draw the shaded solid into the depth buffer for hidden line removal, - // and if we're actually going to display it, to the color buffer too. - glEnable(GL_LIGHTING); - if(!SS.GW.showShaded) glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); - ssglFillMesh(useSpecColor, specColor, &displayMesh, mh, ms1, ms2); - if(!SS.GW.showShaded) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - glDisable(GL_LIGHTING); - } + case DrawMeshAs::HOVERED: { + Canvas::Fill fill = {}; + fill.color = Style::Color(Style::HOVERED); + fill.pattern = Canvas::FillPattern::CHECKERED_A; + fill.zIndex = 2; + Canvas::hFill hcf = canvas->GetFill(fill); - if(SS.GW.showEdges) { - Vector projDir = SS.GW.projRight.Cross(SS.GW.projUp); - - glDepthMask(GL_FALSE); - if(SS.GW.showHdnLines) { - ssglDepthRangeOffset(0); - glDepthFunc(GL_GREATER); - ssglDrawEdges(&displayEdges, /*endpointsToo=*/false, { Style::HIDDEN_EDGE }); - ssglDrawOutlines(&displayOutlines, projDir, { Style::HIDDEN_EDGE }); - glDepthFunc(GL_LEQUAL); + std::vector faces; + hEntity he = SS.GW.hover.entity; + if(he.v != 0 && SK.GetEntity(he)->IsFace()) { + faces.push_back(he.v); + } + canvas->DrawFaces(displayMesh, faces, hcf); + break; } - ssglDepthRangeOffset(2); - ssglDrawEdges(&displayEdges, /*endpointsToo=*/false, { Style::SOLID_EDGE }); - if(SS.GW.showOutlines) { - ssglDrawOutlines(&displayOutlines, projDir, { Style::OUTLINE }); - } else { - ssglDrawOutlines(&displayOutlines, projDir, { Style::SOLID_EDGE }); - } - glDepthMask(GL_TRUE); - } - if(SS.GW.showMesh) ssglDebugMesh(&displayMesh); + case DrawMeshAs::SELECTED: { + Canvas::Fill fill = {}; + fill.color = Style::Color(Style::SELECTED); + fill.pattern = Canvas::FillPattern::CHECKERED_B; + fill.zIndex = 1; + Canvas::hFill hcf = canvas->GetFill(fill); + + std::vector faces; + SS.GW.GroupSelection(); + if(gs.faces > 0) faces.push_back(gs.face[0].v); + if(gs.faces > 1) faces.push_back(gs.face[1].v); + canvas->DrawFaces(displayMesh, faces, hcf); + break; + } + } } -void Group::Draw() { +void Group::Draw(Canvas *canvas) { // Everything here gets drawn whether or not the group is hidden; we // can control this stuff independently, with show/hide solids, edges, // mesh, etc. GenerateDisplayItems(); - DrawDisplayItems(type); + DrawMesh(DrawMeshAs::DEFAULT, canvas); - if(!SS.checkClosedContour) return; + if(SS.GW.showEdges) { + Canvas::Stroke strokeEdge = {}; + strokeEdge.zIndex = 1; + strokeEdge.color = Style::Color(Style::SOLID_EDGE); + strokeEdge.width = Style::Width(Style::SOLID_EDGE); + Canvas::hStroke hcsEdge = canvas->GetStroke(strokeEdge); + + canvas->DrawEdges(displayEdges, hcsEdge); + + if(SS.GW.showHdnLines) { + Canvas::Stroke strokeHidden = strokeEdge; + strokeHidden.layer = Canvas::Layer::OCCLUDED; + strokeHidden.width = Style::Width(Style::HIDDEN_EDGE); + strokeHidden.stipplePattern = Style::PatternType({ Style::HIDDEN_EDGE }); + strokeHidden.stippleScale = Style::StippleScaleMm({ Style::HIDDEN_EDGE }); + Canvas::hStroke hcsHidden = canvas->GetStroke(strokeHidden); + + canvas->DrawEdges(displayEdges, hcsHidden); + canvas->DrawOutlines(displayOutlines, hcsHidden); + } + + if(SS.GW.showOutlines) { + Canvas::Stroke strokeOutline = strokeEdge; + strokeOutline.color = Style::Color(Style::OUTLINE); + strokeOutline.width = Style::Width(Style::OUTLINE); + Canvas::hStroke hcsOutline = canvas->GetStroke(strokeOutline); + + canvas->DrawOutlines(displayOutlines, hcsOutline); + } else { + canvas->DrawOutlines(displayOutlines, hcsEdge); + } + } +} + +void Group::DrawPolyError(Canvas *canvas) { + const Camera &camera = canvas->GetCamera(); + + Canvas::Stroke strokeUnclosed = {}; + strokeUnclosed.color = Style::Color(Style::DRAW_ERROR).WithAlpha(50); + strokeUnclosed.width = Style::Width(Style::DRAW_ERROR); + Canvas::hStroke hcsUnclosed = canvas->GetStroke(strokeUnclosed); + + Canvas::Stroke strokeError = {}; + strokeError.layer = Canvas::Layer::FRONT; + strokeError.color = Style::Color(Style::DRAW_ERROR); + Canvas::hStroke hcsError = canvas->GetStroke(strokeError); + + double textHeight = Style::DefaultTextHeight() / camera.scale; // And finally show the polygons too, and any errors if it's not possible // to assemble the lines into closed polygons. @@ -536,28 +590,16 @@ void Group::Draw() { // Report this error only in sketch-in-workplane groups; otherwise // it's just a nuisance. if(type == Type::DRAWING_WORKPLANE) { - glDisable(GL_DEPTH_TEST); - ssglColorRGBa(Style::Color(Style::DRAW_ERROR), 0.2); - ssglLineWidth (Style::Width(Style::DRAW_ERROR)); - glBegin(GL_LINES); - ssglVertex3v(polyError.notClosedAt.a); - ssglVertex3v(polyError.notClosedAt.b); - glEnd(); - ssglColorRGB(Style::Color(Style::DRAW_ERROR)); - ssglWriteText("not closed contour, or not all same style!", - Style::DefaultTextHeight(), - polyError.notClosedAt.b, SS.GW.projRight, SS.GW.projUp, - NULL, NULL); - glEnable(GL_DEPTH_TEST); + canvas->DrawVectorText("not closed contour, or not all same style!", textHeight, + polyError.notClosedAt.b, camera.projRight, camera.projUp, + hcsError); + canvas->DrawLine(polyError.notClosedAt.a, polyError.notClosedAt.b, hcsUnclosed); } } else if(polyError.how == PolyError::NOT_COPLANAR || polyError.how == PolyError::SELF_INTERSECTING || - polyError.how == PolyError::ZERO_LEN_EDGE) - { + polyError.how == PolyError::ZERO_LEN_EDGE) { // These errors occur at points, not lines if(type == Type::DRAWING_WORKPLANE) { - glDisable(GL_DEPTH_TEST); - ssglColorRGB(Style::Color(Style::DRAW_ERROR)); const char *msg; if(polyError.how == PolyError::NOT_COPLANAR) { msg = "points not all coplanar!"; @@ -566,52 +608,41 @@ void Group::Draw() { } else { msg = "zero-length edge!"; } - ssglWriteText(msg, Style::DefaultTextHeight(), - polyError.errorPointAt, SS.GW.projRight, SS.GW.projUp, - NULL, NULL); - glEnable(GL_DEPTH_TEST); + canvas->DrawVectorText(msg, textHeight, + polyError.errorPointAt, camera.projRight, camera.projUp, + hcsError); } } else { // The contours will get filled in DrawFilledPaths. } } -void Group::FillLoopSetAsPolygon(SBezierLoopSet *sbls) { - SPolygon sp = {}; - sbls->MakePwlInto(&sp); - ssglDepthRangeOffset(1); - ssglFillPolygon(&sp); - ssglDepthRangeOffset(0); - sp.Clear(); -} +void Group::DrawFilledPaths(Canvas *canvas) { + for(const SBezierLoopSet &sbls : bezierLoops.l) { + if(sbls.l.n == 0 || sbls.l.elem[0].l.n == 0) continue; -void Group::DrawFilledPaths() { - SBezierLoopSet *sbls; - SBezierLoopSetSet *sblss = &bezierLoops; - for(sbls = sblss->l.First(); sbls; sbls = sblss->l.NextAfter(sbls)) { - if(sbls->l.n == 0 || sbls->l.elem[0].l.n == 0) continue; // In an assembled loop, all the styles should be the same; so doesn't // matter which one we grab. - SBezier *sb = &(sbls->l.elem[0].l.elem[0]); - hStyle hs = { (uint32_t)sb->auxA }; - Style *s = Style::Get(hs); + SBezier *sb = &(sbls.l.elem[0].l.elem[0]); + Style *s = Style::Get({ (uint32_t)sb->auxA }); + + Canvas::Fill fill = {}; + fill.zIndex = 1; if(s->filled) { // This is a filled loop, where the user specified a fill color. - ssglColorRGBa(s->fillColor, 1); - FillLoopSetAsPolygon(sbls); - } else { - if(h.v == SS.GW.activeGroup.v && SS.checkClosedContour && - polyError.how == PolyError::GOOD) - { - // If this is the active group, and we are supposed to check - // for closed contours, and we do indeed have a closed and - // non-intersecting contour, then fill it dimly. - ssglColorRGBa(Style::Color(Style::CONTOUR_FILL), 0.5); - ssglDepthRangeOffset(1); - FillLoopSetAsPolygon(sbls); - ssglDepthRangeOffset(0); - } - } + fill.color = s->color; + } else if(h.v == SS.GW.activeGroup.v && SS.checkClosedContour && + polyError.how == PolyError::GOOD) { + // If this is the active group, and we are supposed to check + // for closed contours, and we do indeed have a closed and + // non-intersecting contour, then fill it dimly. + fill.color = Style::Color(Style::CONTOUR_FILL).WithAlpha(127); + } else continue; + Canvas::hFill hcf = canvas->GetFill(fill); + + SPolygon sp = {}; + sbls.MakePwlInto(&sp); + canvas->DrawPolygon(sp, hcf); } } diff --git a/src/importdxf.cpp b/src/importdxf.cpp index 12c7e530..af0d9e2a 100644 --- a/src/importdxf.cpp +++ b/src/importdxf.cpp @@ -2,11 +2,6 @@ #include "libdxfrw.h" #include "libdwgr.h" -#ifdef WIN32 -// Conflicts with DRW::TEXT. -# undef TEXT -#endif - namespace SolveSpace { static std::string ToUpper(std::string str) { diff --git a/src/mouse.cpp b/src/mouse.cpp index a0334960..2aebbafb 100644 --- a/src/mouse.cpp +++ b/src/mouse.cpp @@ -583,8 +583,7 @@ void GraphicsWindow::MouseRightUp(double x, double y) { Request *r = SK.GetRequest(gs.entity[0].request()); if(r->type == Request::Type::CUBIC || r->type == Request::Type::CUBIC_PERIODIC) { Entity *e = SK.GetEntity(gs.entity[0]); - e->GetDistance(Point2d::From(x, y)); - addAfterPoint = e->dogd.data; + addAfterPoint = e->GetPositionOfPoint(GetCamera(), Point2d::From(x, y)); ssassert(addAfterPoint != -1, "Expected a nearest bezier point to be located"); // Skip derivative point. if(r->type == Request::Type::CUBIC) addAfterPoint++; @@ -1253,7 +1252,7 @@ void GraphicsWindow::MouseLeftDoubleClick(double mx, double my) { return; } - Vector p3 = c->GetLabelPos(); + Vector p3 = c->GetLabelPos(GetCamera()); Point2d p2 = ProjectPoint(p3); std::string editValue; @@ -1300,7 +1299,7 @@ void GraphicsWindow::MouseLeftDoubleClick(double mx, double my) { hStyle hs = c->disp.style; if(hs.v == 0) hs.v = Style::CONSTRAINT; ShowGraphicsEditControl((int)p2.x, (int)p2.y, - (int)(ssglStrFontSize(Style::TextHeight(hs)) * scale), + (int)(VectorFont::Builtin()->GetHeight(Style::TextHeight(hs))), editMinWidthChar, editValue); } } diff --git a/src/platform/cocoamain.mm b/src/platform/cocoamain.mm index 3bfb9916..21208ef8 100644 --- a/src/platform/cocoamain.mm +++ b/src/platform/cocoamain.mm @@ -19,12 +19,6 @@ using SolveSpace::dbp; -#define GL_CHECK() \ - do { \ - int err = (int)glGetError(); \ - if(err) dbp("%s:%d: glGetError() == 0x%X", __FILE__, __LINE__, err); \ - } while (0) - /* Settings */ namespace SolveSpace { @@ -116,6 +110,8 @@ void SolveSpace::ScheduleLater() { /* OpenGL view */ +const bool SolveSpace::FLIP_FRAMEBUFFER = false; + @interface GLViewWithEditor : NSView - (void)drawGL; @@ -200,9 +196,8 @@ CONVERT(Rect) offscreen->begin(width, height); [self drawGL]; - GL_CHECK(); - uint8_t *pixels = offscreen->end(![self isFlipped]); + uint8_t *pixels = offscreen->end(); CGDataProviderRef provider = CGDataProviderCreateWithData( NULL, pixels, width * height * 4, NULL); CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB(); diff --git a/src/platform/gloffscreen.cpp b/src/platform/gloffscreen.cpp index 84ee0e24..b0738936 100644 --- a/src/platform/gloffscreen.cpp +++ b/src/platform/gloffscreen.cpp @@ -12,7 +12,7 @@ #include "gloffscreen.h" #include "solvespace.h" -GLOffscreen::GLOffscreen() : _pixels(NULL), _pixels_inv(NULL), _width(0), _height(0) { +GLOffscreen::GLOffscreen() : _pixels(NULL), _width(0), _height(0) { #ifndef __APPLE__ ssassert(glewInit() == GLEW_OK, "Unexpected GLEW failure"); #endif @@ -26,7 +26,6 @@ GLOffscreen::GLOffscreen() : _pixels(NULL), _pixels_inv(NULL), _width(0), _heigh GLOffscreen::~GLOffscreen() { delete[] _pixels; - delete[] _pixels_inv; glDeleteRenderbuffersEXT(1, &_depth_renderbuffer); glDeleteRenderbuffersEXT(1, &_color_renderbuffer); glDeleteFramebuffersEXT(1, &_framebuffer); @@ -35,10 +34,7 @@ GLOffscreen::~GLOffscreen() { bool GLOffscreen::begin(int width, int height) { if(_width != width || _height != height) { delete[] _pixels; - delete[] _pixels_inv; - - _pixels = new uint32_t[width * height * 4]; - _pixels_inv = new uint32_t[width * height * 4]; + _pixels = new uint8_t[width * height * 4]; _width = width; _height = height; @@ -63,22 +59,16 @@ bool GLOffscreen::begin(int width, int height) { return false; } -uint8_t *GLOffscreen::end(bool flip) { +uint8_t *GLOffscreen::end() { #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ glReadPixels(0, 0, _width, _height, - GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, _pixels_inv); + GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, _pixels); #else glReadPixels(0, 0, _width, _height, - GL_BGRA, GL_UNSIGNED_INT_8_8_8_8, _pixels_inv); + GL_BGRA, GL_UNSIGNED_INT_8_8_8_8, _pixels); #endif - if(flip) { - /* in OpenGL coordinates, bottom is zero Y */ - for(int i = 0; i < _height; i++) - memcpy(&_pixels[_width * i], &_pixels_inv[_width * (_height - i - 1)], _width * 4); - } - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); - return (uint8_t*) (flip ? _pixels : _pixels_inv); + return _pixels; } diff --git a/src/platform/gloffscreen.h b/src/platform/gloffscreen.h index c897be3b..378e0447 100644 --- a/src/platform/gloffscreen.h +++ b/src/platform/gloffscreen.h @@ -24,12 +24,12 @@ public: the pixel format is ARGB32 with top row at index 0 if flip is true and bottom row at index 0 if flip is false. the returned array is valid until the next call to begin() */ - uint8_t *end(bool flip = true); + uint8_t *end(); private: unsigned int _framebuffer; unsigned int _color_renderbuffer, _depth_renderbuffer; - uint32_t *_pixels, *_pixels_inv; + uint8_t *_pixels; int _width, _height; }; diff --git a/src/platform/gtkmain.cpp b/src/platform/gtkmain.cpp index 44853bbf..474fea2b 100644 --- a/src/platform/gtkmain.cpp +++ b/src/platform/gtkmain.cpp @@ -224,12 +224,7 @@ void ScheduleLater() { /* GL wrapper */ -#define GL_CHECK() \ - do { \ - int err = (int)glGetError(); \ - if(err) dbp("%s:%d: glGetError() == 0x%X %s", \ - __FILE__, __LINE__, err, gluErrorString(err)); \ - } while (0) +const bool FLIP_FRAMEBUFFER = true; class GlWidget : public Gtk::DrawingArea { public: @@ -327,8 +322,6 @@ protected: "Cannot allocate offscreen rendering buffer"); on_gl_draw(); - glFlush(); - GL_CHECK(); Cairo::RefPtr surface = Cairo::ImageSurface::create( _offscreen->end(), Cairo::FORMAT_RGB24, @@ -1631,10 +1624,11 @@ int main(int argc, char** argv) { CnfLoad(); - SolveSpace::Pixmap icon = LoadPNG("freedesktop/solvespace-48x48.png"); - Glib::RefPtr icon_gdk = - Gdk::Pixbuf::create_from_data(&icon.data[0], Gdk::COLORSPACE_RGB, - icon.hasAlpha, 8, icon.width, icon.height, icon.stride); + auto icon = LoadPng("freedesktop/solvespace-48x48.png"); + auto icon_gdk = + Gdk::Pixbuf::create_from_data(&icon->data[0], Gdk::COLORSPACE_RGB, + icon->format == SolveSpace::Pixmap::Format::RGBA, 8, + icon->width, icon->height, icon->stride); TW.reset(new TextWindowGtk); GW.reset(new GraphicsWindowGtk); diff --git a/src/platform/unixutil.cpp b/src/platform/unixutil.cpp index ed0789a9..41703f79 100644 --- a/src/platform/unixutil.cpp +++ b/src/platform/unixutil.cpp @@ -9,6 +9,9 @@ //----------------------------------------------------------------------------- #include #include +#ifdef __APPLE__ +# include // for strcasecmp +#endif #include "solvespace.h" @@ -51,6 +54,17 @@ void assert_failure(const char *file, unsigned line, const char *function, abort(); } +bool PathEqual(const std::string &a, const std::string &b) +{ +#if defined(__APPLE__) + // Case-sensitivity is actually per-volume on OS X, + // but it is tedious to implement and test for little benefit. + return !strcasecmp(a.c_str(), b.c_str()); +#else + return a == b; +#endif +} + FILE *ssfopen(const std::string &filename, const char *mode) { ssassert(filename.length() == strlen(filename.c_str()), diff --git a/src/platform/w32main.cpp b/src/platform/w32main.cpp index c5d51306..a74a05a7 100644 --- a/src/platform/w32main.cpp +++ b/src/platform/w32main.cpp @@ -6,14 +6,16 @@ // Copyright 2008-2013 Jonathan Westhues. //----------------------------------------------------------------------------- #include + +#include "config.h" +#include "solvespace.h" + +// Include after solvespace.h to avoid identifier clashes. #include #include #include #include -#include "solvespace.h" -#include "config.h" - #ifdef HAVE_SPACEWARE # include # include @@ -725,6 +727,8 @@ void SolveSpace::ShowTextWindow(bool visible) ShowWindow(TextWnd, visible ? SW_SHOWNOACTIVATE : SW_HIDE); } +const bool SolveSpace::FLIP_FRAMEBUFFER = false; + static void CreateGlContext(HWND hwnd, HGLRC *glrc) { HDC hdc = GetDC(hwnd); @@ -1378,13 +1382,6 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, ShowWindow(TextWnd, SW_SHOWNOACTIVATE); ShowWindow(GraphicsWnd, SW_SHOW); - glClearColor(0, 0, 0, 1); - glClear(GL_COLOR_BUFFER_BIT); - SwapBuffers(GetDC(GraphicsWnd)); - glClearColor(0, 0, 0, 1); - glClear(GL_COLOR_BUFFER_BIT); - SwapBuffers(GetDC(GraphicsWnd)); - // Create the heaps for all dynamic memory (AllocTemporary, MemAlloc) InitHeaps(); diff --git a/src/platform/w32util.cpp b/src/platform/w32util.cpp index a79642c7..a207ae18 100644 --- a/src/platform/w32util.cpp +++ b/src/platform/w32util.cpp @@ -8,6 +8,9 @@ //----------------------------------------------------------------------------- #include "solvespace.h" +// Include after solvespace.h to avoid identifier clashes. +#include + namespace SolveSpace { static HANDLE PermHeap, TempHeap; @@ -78,6 +81,16 @@ std::wstring Widen(const std::string &in) return out; } +bool PathEqual(const std::string &a, const std::string &b) +{ + // Case-sensitivity is actually per-volume on Windows, + // but it is tedious to implement and test for little benefit. + std::wstring wa = Widen(a), wb = Widen(b); + return std::equal(wa.begin(), wa.end(), wb.begin(), /*wb.end(),*/ + [](wchar_t wca, wchar_t wcb) { return towlower(wca) == towlower(wcb); }); + +} + FILE *ssfopen(const std::string &filename, const char *mode) { // Prepend \\?\ UNC prefix unless already an UNC path. diff --git a/src/polygon.h b/src/polygon.h index 66fb2aa0..29445504 100644 --- a/src/polygon.h +++ b/src/polygon.h @@ -26,7 +26,7 @@ enum class BspClass : uint32_t { NEG = 101, COPLANAR = 200 }; - + enum class EdgeKind : uint32_t { NAKED_OR_SELF_INTER = 100, SELF_INTER = 200, @@ -207,8 +207,6 @@ public: static SBsp2 *InsertOrCreateEdge(SBsp2 *where, SEdge *nedge, Vector nnp, Vector out); static SBsp2 *Alloc(); - - void DebugDraw(Vector n, double d) const; }; class SBsp3 { @@ -240,8 +238,6 @@ public: void InsertInPlane(bool pos2, STriangle *tr, SMesh *m); void GenerateInPaintOrder(SMesh *m) const; - - void DebugDraw() const; }; class SMesh { diff --git a/src/render/render.cpp b/src/render/render.cpp new file mode 100644 index 00000000..15c13ec0 --- /dev/null +++ b/src/render/render.cpp @@ -0,0 +1,353 @@ +//----------------------------------------------------------------------------- +// Backend-agnostic rendering interface, and various backends we use. +// +// Copyright 2016 whitequark +//----------------------------------------------------------------------------- +#include "solvespace.h" + +namespace SolveSpace { + +//----------------------------------------------------------------------------- +// Camera transformations. +//----------------------------------------------------------------------------- + +Point2d Camera::ProjectPoint(Vector p) const { + Vector p3 = ProjectPoint3(p); + Point2d p2 = { p3.x, p3.y }; + return p2; +} + +Vector Camera::ProjectPoint3(Vector p) const { + double w; + Vector r = ProjectPoint4(p, &w); + return r.ScaledBy(scale/w); +} + +Vector Camera::ProjectPoint4(Vector p, double *w) const { + p = p.Plus(offset); + + Vector r; + r.x = p.Dot(projRight); + r.y = p.Dot(projUp); + r.z = p.Dot(projUp.Cross(projRight)); + + *w = 1 + r.z*tangent*scale; + return r; +} + +Vector Camera::UnProjectPoint(Point2d p) const { + Vector orig = offset.ScaledBy(-1); + + // Note that we're ignoring the effects of perspective. Since our returned + // point has the same component normal to the screen as the offset, it + // will have z = 0 after the rotation is applied, thus w = 1. So this is + // correct. + orig = orig.Plus(projRight.ScaledBy(p.x / scale)).Plus( + projUp. ScaledBy(p.y / scale)); + return orig; +} + +Vector Camera::UnProjectPoint3(Vector p) const { + p.z = p.z / (scale - p.z * tangent * scale); + double w = 1 + p.z * tangent * scale; + p.x *= w / scale; + p.y *= w / scale; + + Vector orig = offset.ScaledBy(-1); + orig = orig.Plus(projRight.ScaledBy(p.x)).Plus( + projUp. ScaledBy(p.y).Plus( + projRight.Cross(projUp). ScaledBy(p.z))); + return orig; +} + +Vector Camera::VectorFromProjs(Vector rightUpForward) const { + Vector n = projRight.Cross(projUp); + + Vector r = (projRight.ScaledBy(rightUpForward.x)); + r = r.Plus(projUp.ScaledBy(rightUpForward.y)); + r = r.Plus(n.ScaledBy(rightUpForward.z)); + return r; +} + +Vector Camera::AlignToPixelGrid(Vector v) const { + if(!hasPixels) return v; + + v = ProjectPoint3(v); + v.x = floor(v.x) + 0.5; + v.y = floor(v.y) + 0.5; + return UnProjectPoint3(v); +} + +void Camera::LoadIdentity() { + offset = { 0.0, 0.0, 0.0 }; + projRight = { 1.0, 0.0, 0.0 }; + projUp = { 0.0, 1.0, 0.0 }; + scale = 1.0; + tangent = 0.0; +} + +void Camera::NormalizeProjectionVectors() { + if(projRight.Magnitude() < LENGTH_EPS) { + projRight = Vector::From(1, 0, 0); + } + + Vector norm = projRight.Cross(projUp); + // If projRight and projUp somehow ended up parallel, then pick an + // arbitrary projUp normal to projRight. + if(norm.Magnitude() < LENGTH_EPS) { + norm = projRight.Normal(0); + } + projUp = norm.Cross(projRight); + + projUp = projUp.WithMagnitude(1); + projRight = projRight.WithMagnitude(1); +} + +//----------------------------------------------------------------------------- +// Stroke and fill caching. +//----------------------------------------------------------------------------- + +bool Canvas::Stroke::Equals(const Stroke &other) const { + return (layer == other.layer && + zIndex == other.zIndex && + color.Equals(other.color) && + width == other.width && + stipplePattern == other.stipplePattern && + stippleScale == other.stippleScale); +} + +bool Canvas::Fill::Equals(const Fill &other) const { + return (layer == other.layer && + zIndex == other.zIndex && + color.Equals(other.color) && + pattern == other.pattern); +} + +Canvas::hStroke Canvas::GetStroke(const Stroke &stroke) { + for(const Stroke &s : strokes) { + if(s.Equals(stroke)) return s.h; + } + Stroke strokeCopy = stroke; + return strokes.AddAndAssignId(&strokeCopy); +} + +Canvas::hFill Canvas::GetFill(const Fill &fill) { + for(const Fill &f : fills) { + if(f.Equals(fill)) return f.h; + } + Fill fillCopy = fill; + return fills.AddAndAssignId(&fillCopy); +} + +//----------------------------------------------------------------------------- +// A wrapper around Canvas that simplifies drawing UI in screen coordinates +//----------------------------------------------------------------------------- + +void UiCanvas::DrawLine(int x1, int y1, int x2, int y2, RgbaColor color, int width) { + Vector va = { (double)x1 + 0.5, (double)Flip(y1) + 0.5, 0.0 }, + vb = { (double)x2 + 0.5, (double)Flip(y2) + 0.5, 0.0 }; + + Canvas::Stroke stroke = {}; + stroke.layer = Canvas::Layer::FRONT; + stroke.width = (double)width; + stroke.color = color; + Canvas::hStroke hcs = canvas->GetStroke(stroke); + + canvas->DrawLine(va, vb, hcs); +} + +void UiCanvas::DrawRect(int l, int r, int t, int b, + RgbaColor fillColor, RgbaColor outlineColor) { + Vector va = { (double)l + 0.5, (double)Flip(b) + 0.5, 0.0 }, + vb = { (double)l + 0.5, (double)Flip(t) + 0.5, 0.0 }, + vc = { (double)r + 0.5, (double)Flip(t) + 0.5, 0.0 }, + vd = { (double)r + 0.5, (double)Flip(b) + 0.5, 0.0 }; + + if(!fillColor.IsEmpty()) { + Canvas::Fill fill = {}; + fill.layer = Canvas::Layer::FRONT; + fill.color = fillColor; + Canvas::hFill hcf = canvas->GetFill(fill); + + canvas->DrawQuad(va, vb, vc, vd, hcf); + } + + if(!outlineColor.IsEmpty()) { + Canvas::Stroke stroke = {}; + stroke.layer = Canvas::Layer::FRONT; + stroke.width = 1.0; + stroke.color = outlineColor; + Canvas::hStroke hcs = canvas->GetStroke(stroke); + + canvas->DrawLine(va, vb, hcs); + canvas->DrawLine(vb, vc, hcs); + canvas->DrawLine(vc, vd, hcs); + canvas->DrawLine(vd, va, hcs); + } +} + +void UiCanvas::DrawPixmap(std::shared_ptr pm, int x, int y) { + Canvas::Fill fill = {}; + fill.layer = Canvas::Layer::FRONT; + fill.color = { 0, 0, 0, 255 }; + Canvas::hFill hcf = canvas->GetFill(fill); + + canvas->DrawPixmap(pm, + { (double)x, (double)(flip ? Flip(y) - pm->height : y), 0.0 }, + { (double)pm->width, 0.0, 0.0 }, + { 0.0, (double)pm->height, 0.0 }, + { 0.0, 1.0 }, + { 1.0, 0.0 }, + hcf); +} + +void UiCanvas::DrawBitmapChar(char32_t codepoint, int x, int y, RgbaColor color) { + BitmapFont *font = BitmapFont::Builtin(); + + Canvas::Fill fill = {}; + fill.layer = Canvas::Layer::FRONT; + fill.color = color; + Canvas::hFill hcf = canvas->GetFill(fill); + + if(codepoint >= 0xe000 && codepoint <= 0xefff) { + // Special character, like a checkbox or a radio button + x -= 3; + } + + double s0, t0, s1, t1; + size_t w, h; + if(font->LocateGlyph(codepoint, &s0, &t0, &s1, &t1, &w, &h)) { + // LocateGlyph modified the texture, reload it. + canvas->InvalidatePixmap(font->texture); + } + + canvas->DrawPixmap(font->texture, + { (double)x, (double)Flip(y), 0.0 }, + { (double)w, 0.0, 0.0 }, + { 0.0, (double) h, 0.0 }, + { s0, t1 }, + { s1, t0 }, + hcf); +} + +void UiCanvas::DrawBitmapText(const std::string &str, int x, int y, RgbaColor color) { + BitmapFont *font = BitmapFont::Builtin(); + + for(char32_t codepoint : ReadUTF8(str)) { + DrawBitmapChar(codepoint, x, y, color); + x += font->GetWidth(codepoint) * 8; + } +} + +//----------------------------------------------------------------------------- +// A canvas that performs picking against drawn geometry. +//----------------------------------------------------------------------------- + +void ObjectPicker::DoCompare(double distance, int zIndex, int comparePosition) { + if(distance > selRadius) return; + if((zIndex == maxZIndex && distance < minDistance) || (zIndex > maxZIndex)) { + minDistance = distance; + maxZIndex = zIndex; + position = comparePosition; + } +} + +void ObjectPicker::DoQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d, + int zIndex, int comparePosition) { + Point2d corners[4] = { + camera.ProjectPoint(a), + camera.ProjectPoint(b), + camera.ProjectPoint(c), + camera.ProjectPoint(d) + }; + double minNegative = VERY_NEGATIVE, + maxPositive = VERY_POSITIVE; + for(int i = 0; i < 4; i++) { + Point2d ap = corners[i], + bp = corners[(i + 1) % 4]; + double distance = point.DistanceToLineSigned(ap, bp.Minus(ap), /*asSegment=*/true); + if(distance < 0) minNegative = std::max(minNegative, distance); + if(distance > 0) maxPositive = std::min(maxPositive, distance); + } + + bool insideQuad = (minNegative == VERY_NEGATIVE || maxPositive == VERY_POSITIVE); + if(insideQuad) { + DoCompare(0.0, zIndex, comparePosition); + } else { + double distance = std::min(fabs(minNegative), fabs(maxPositive)); + DoCompare(distance, zIndex, comparePosition); + } +} + +void ObjectPicker::DrawLine(const Vector &a, const Vector &b, hStroke hcs) { + Stroke *stroke = strokes.FindById(hcs); + Point2d ap = camera.ProjectPoint(a); + Point2d bp = camera.ProjectPoint(b); + double distance = point.DistanceToLine(ap, bp.Minus(ap), /*asSegment=*/true); + DoCompare(distance - stroke->width / 2.0, stroke->zIndex); +} + +void ObjectPicker::DrawEdges(const SEdgeList &el, hStroke hcs) { + Stroke *stroke = strokes.FindById(hcs); + for(const SEdge &e : el.l) { + Point2d ap = camera.ProjectPoint(e.a); + Point2d bp = camera.ProjectPoint(e.b); + double distance = point.DistanceToLine(ap, bp.Minus(ap), /*asSegment=*/true); + DoCompare(distance - stroke->width / 2.0, stroke->zIndex, e.auxB); + } +} + +void ObjectPicker::DrawOutlines(const SOutlineList &ol, hStroke hcs) { + ssassert(false, "Not implemented"); +} + +void ObjectPicker::DrawVectorText(const std::string &text, double height, + const Vector &o, const Vector &u, const Vector &v, + hStroke hcs) { + Stroke *stroke = strokes.FindById(hcs); + double w = VectorFont::Builtin()-> GetWidth(height, text), + h = VectorFont::Builtin()->GetHeight(height); + DoQuad(o, + o.Plus(v.ScaledBy(h)), + o.Plus(u.ScaledBy(w)).Plus(v.ScaledBy(h)), + o.Plus(u.ScaledBy(w)), + stroke->zIndex); +} + +void ObjectPicker::DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d, + hFill hcf) { + Fill *fill = fills.FindById(hcf); + DoQuad(a, b, c, d, fill->zIndex); +} + +void ObjectPicker::DrawPoint(const Vector &o, double s, hFill hcf) { + Fill *fill = fills.FindById(hcf); + double distance = point.DistanceTo(camera.ProjectPoint(o)) - s / 2; + DoCompare(distance, fill->zIndex); +} + +void ObjectPicker::DrawPolygon(const SPolygon &p, hFill hcf) { + ssassert(false, "Not implemented"); +} + +void ObjectPicker::DrawMesh(const SMesh &m, hFill hcfFront, hFill hcfBack, hStroke hcsTriangles) { + ssassert(false, "Not implemented"); +} + +void ObjectPicker::DrawFaces(const SMesh &m, const std::vector &faces, hFill hcf) { + ssassert(false, "Not implemented"); +} + +void ObjectPicker::DrawPixmap(std::shared_ptr pm, + const Vector &o, const Vector &u, const Vector &v, + const Point2d &ta, const Point2d &tb, Canvas::hFill hcf) { + ssassert(false, "Not implemented"); +} + +bool ObjectPicker::Pick(std::function drawFn) { + minDistance = VERY_POSITIVE; + drawFn(); + return minDistance < selRadius; +} + +} diff --git a/src/render/render.h b/src/render/render.h new file mode 100644 index 00000000..38e7d88c --- /dev/null +++ b/src/render/render.h @@ -0,0 +1,254 @@ +//----------------------------------------------------------------------------- +// Backend-agnostic rendering interface, and various backends we use. +// +// Copyright 2016 whitequark +//----------------------------------------------------------------------------- + +#ifndef SOLVESPACE_RENDER_H +#define SOLVESPACE_RENDER_H + +enum class StipplePattern : uint32_t; + +// A mapping from 3d sketch coordinates to 2d screen coordinates, using +// an axonometric projection. +class Camera { +public: + size_t width, height; + Vector offset; + Vector projRight; + Vector projUp; + double scale; + double tangent; + bool hasPixels; + + bool IsPerspective() const { return tangent != 0.0; } + + Point2d ProjectPoint(Vector p) const; + Vector ProjectPoint3(Vector p) const; + Vector ProjectPoint4(Vector p, double *w) const; + Vector UnProjectPoint(Point2d p) const; + Vector UnProjectPoint3(Vector p) const; + Vector VectorFromProjs(Vector rightUpForward) const; + Vector AlignToPixelGrid(Vector v) const; + + void LoadIdentity(); + void NormalizeProjectionVectors(); +}; + +// A description of scene lighting. +class Lighting { +public: + RgbaColor backgroundColor; + double ambientIntensity; + double lightIntensity[2]; + Vector lightDirection[2]; +}; + +// An interface for populating a drawing area with geometry. +class Canvas { +public: + // Stroke and fill styles are addressed with handles to be able to quickly + // group geometry into indexed draw calls. + class hStroke { + public: + uint32_t v; + }; + + class hFill { + public: + uint32_t v; + }; + + // The layer of a geometry describes how it occludes other geometry. + // Within a layer, geometry with higher z-index occludes geometry with lower z-index, + // or geometry drawn earlier if z-indexes match. + enum class Layer { + NORMAL, // Occluded by geometry with lower Z coordinate + OCCLUDED, // Only drawn over geometry with lower Z coordinate + DEPTH_ONLY, // Like NORMAL, but only affects future occlusion, not color + BACK, // Always drawn below all other geometry + FRONT, // Always drawn above all other geometry + }; + + class Stroke { + public: + hStroke h; + + Layer layer; + int zIndex; + RgbaColor color; + double width; + StipplePattern stipplePattern; + double stippleScale; + + bool Equals(const Stroke &other) const; + }; + + enum class FillPattern { + SOLID, CHECKERED_A, CHECKERED_B + }; + + class Fill { + public: + hFill h; + + Layer layer; + int zIndex; + RgbaColor color; + FillPattern pattern; + + bool Equals(const Fill &other) const; + }; + + IdList strokes; + IdList fills; + + Canvas() : strokes(), fills() {}; + + hStroke GetStroke(const Stroke &stroke); + hFill GetFill(const Fill &fill); + + virtual const Camera &GetCamera() const = 0; + + virtual void DrawLine(const Vector &a, const Vector &b, hStroke hcs) = 0; + virtual void DrawEdges(const SEdgeList &el, hStroke hcs) = 0; + virtual bool DrawBeziers(const SBezierList &bl, hStroke hcs) = 0; + virtual void DrawOutlines(const SOutlineList &ol, hStroke hcs) = 0; + virtual void DrawVectorText(const std::string &text, double height, + const Vector &o, const Vector &u, const Vector &v, + hStroke hcs) = 0; + + virtual void DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d, + hFill hcf) = 0; + virtual void DrawPoint(const Vector &o, double d, hFill hcf) = 0; + virtual void DrawPolygon(const SPolygon &p, hFill hcf) = 0; + virtual void DrawMesh(const SMesh &m, hFill hcfFront, hFill hcfBack = {}, + hStroke hcsTriangles = {}) = 0; + virtual void DrawFaces(const SMesh &m, const std::vector &faces, hFill hcf) = 0; + + virtual void DrawPixmap(std::shared_ptr pm, + const Vector &o, const Vector &u, const Vector &v, + const Point2d &ta, const Point2d &tb, hFill hcf) = 0; + virtual void InvalidatePixmap(std::shared_ptr pm) = 0; +}; + +// A wrapper around Canvas that simplifies drawing UI in screen coordinates. +class UiCanvas { +public: + Canvas *canvas; + bool flip; + + UiCanvas() : canvas(), flip() {}; + + void DrawLine(int x1, int y1, int x2, int y2, RgbaColor color, int width = 1); + void DrawRect(int l, int r, int t, int b, RgbaColor fillColor, RgbaColor outlineColor); + void DrawPixmap(std::shared_ptr pm, int x, int y); + void DrawBitmapChar(char32_t codepoint, int x, int y, RgbaColor color); + void DrawBitmapText(const std::string &str, int x, int y, RgbaColor color); + + int Flip(int y) const { return flip ? (int)canvas->GetCamera().height - y : y; } +}; + +// A canvas that performs picking against drawn geometry. +class ObjectPicker : public Canvas { +public: + Camera camera; + // Configuration. + Point2d point; + double selRadius; + // Picking state. + double minDistance; + int maxZIndex; + uint32_t position; + + ObjectPicker() : camera(), point(), selRadius(), + minDistance(), maxZIndex(), position() {} + + const Camera &GetCamera() const override { return camera; } + + void DrawLine(const Vector &a, const Vector &b, hStroke hcs) override; + void DrawEdges(const SEdgeList &el, hStroke hcs) override; + bool DrawBeziers(const SBezierList &bl, hStroke hcs) override { return false; } + void DrawOutlines(const SOutlineList &ol, hStroke hcs) override; + void DrawVectorText(const std::string &text, double height, + const Vector &o, const Vector &u, const Vector &v, + hStroke hcs) override; + + void DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d, + hFill hcf) override; + void DrawPoint(const Vector &o, double s, hFill hcf) override; + void DrawPolygon(const SPolygon &p, hFill hcf) override; + void DrawMesh(const SMesh &m, hFill hcfFront, hFill hcfBack, hStroke hcsTriangles) override; + void DrawFaces(const SMesh &m, const std::vector &faces, hFill hcf) override; + + void DrawPixmap(std::shared_ptr pm, + const Vector &o, const Vector &u, const Vector &v, + const Point2d &ta, const Point2d &tb, hFill hcf) override; + void InvalidatePixmap(std::shared_ptr pm) override {} + + void DoCompare(double distance, int zIndex, int comparePosition = 0); + void DoQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d, + int zIndex, int comparePosition = 0); + + bool Pick(std::function drawFn); +}; + +// A canvas that uses the core OpenGL profile, for desktop systems. +class OpenGl1Renderer : public Canvas { +public: + Camera camera; + Lighting lighting; + // Cached OpenGL state. + struct { + bool drawing; + unsigned mode; // GLenum, but we don't include GL.h globally + hStroke hcs; + Stroke *stroke; + hFill hcf; + Fill *fill; + std::weak_ptr texture; + } current; + + OpenGl1Renderer() : camera(), lighting(), current() {} + + const Camera &GetCamera() const override { return camera; } + + void DrawLine(const Vector &a, const Vector &b, hStroke hcs) override; + void DrawEdges(const SEdgeList &el, hStroke hcs) override; + bool DrawBeziers(const SBezierList &bl, hStroke hcs) override { return false; } + void DrawOutlines(const SOutlineList &ol, hStroke hcs) override; + void DrawVectorText(const std::string &text, double height, + const Vector &o, const Vector &u, const Vector &v, + hStroke hcs) override; + + void DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d, + hFill hcf) override; + void DrawPoint(const Vector &o, double s, hFill hcf) override; + void DrawPolygon(const SPolygon &p, hFill hcf) override; + void DrawMesh(const SMesh &m, hFill hcfFront, hFill hcfBack, hStroke hcsTriangles) override; + void DrawFaces(const SMesh &m, const std::vector &faces, hFill hcf) override; + void DrawPixmap(std::shared_ptr pm, + const Vector &o, const Vector &u, const Vector &v, + const Point2d &ta, const Point2d &tb, hFill hcf) override; + void InvalidatePixmap(std::shared_ptr pm) override; + + void SelectPrimitive(unsigned mode); + void UnSelectPrimitive(); + Stroke *SelectStroke(hStroke hcs); + Fill *SelectFill(hFill hcf); + void SelectTexture(std::shared_ptr pm); + void DoFatLineEndcap(const Vector &p, const Vector &u, const Vector &v); + void DoFatLine(const Vector &a, const Vector &b, double width); + void DoLine(const Vector &a, const Vector &b, hStroke hcs); + void DoPoint(Vector p, double radius); + void DoStippledLine(const Vector &a, const Vector &b, hStroke hcs); + + void UpdateProjection(bool flip = FLIP_FRAMEBUFFER); + void BeginFrame(); + void EndFrame(); + std::shared_ptr ReadFrame(); + + static void GetIdent(const char **vendor, const char **renderer, const char **version); +}; + +#endif diff --git a/src/render/rendergl1.cpp b/src/render/rendergl1.cpp new file mode 100644 index 00000000..7cda977f --- /dev/null +++ b/src/render/rendergl1.cpp @@ -0,0 +1,720 @@ +//----------------------------------------------------------------------------- +// OpenGL 1 based rendering interface. +// +// Copyright 2016 whitequark +//----------------------------------------------------------------------------- +#include "config.h" +#include "solvespace.h" + +#ifdef WIN32 +// Include after solvespace.h to avoid identifier clashes. +# include // required by GL headers +#endif +#ifdef __APPLE__ +# include +# include +#else +# include +# include +#endif + +namespace SolveSpace { + +//----------------------------------------------------------------------------- +// Thin wrappers around OpenGL functions to fix bugs, adapt them to our +// data structures, etc. +//----------------------------------------------------------------------------- + +static inline void ssglNormal3v(Vector n) { + glNormal3d(n.x, n.y, n.z); +} + +static inline void ssglVertex3v(Vector v) { + glVertex3d(v.x, v.y, v.z); +} + +void ssglLineWidth(double 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((GLfloat)width); +} + +static inline void ssglColorRGBA(RgbaColor color) { + glColor4d(color.redF(), color.greenF(), color.blueF(), color.alphaF()); +} + +static inline void ssglMaterialRGBA(GLenum side, RgbaColor color) { + GLfloat mpb[] = { color.redF(), color.greenF(), color.blueF(), color.alphaF() }; + glMaterialfv(side, GL_AMBIENT_AND_DIFFUSE, mpb); +} + +static void ssglDepthRange(Canvas::Layer layer, int zIndex) { + switch(layer) { + case Canvas::Layer::NORMAL: + case Canvas::Layer::FRONT: + case Canvas::Layer::BACK: + glDepthFunc(GL_LEQUAL); + glDepthMask(GL_TRUE); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + break; + + case Canvas::Layer::DEPTH_ONLY: + glDepthFunc(GL_LEQUAL); + glDepthMask(GL_TRUE); + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + break; + + case Canvas::Layer::OCCLUDED: + glDepthFunc(GL_GREATER); + glDepthMask(GL_FALSE); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + break; + } + + switch(layer) { + case Canvas::Layer::FRONT: + glDepthRange(0.0, 0.0); + break; + + case Canvas::Layer::BACK: + glDepthRange(1.0, 1.0); + break; + + case Canvas::Layer::NORMAL: + case Canvas::Layer::DEPTH_ONLY: + case Canvas::Layer::OCCLUDED: + // The size of this step depends on the resolution of the Z buffer; for + // a 16-bit buffer, this should be fine. + double offset = 1.0 / (65535 * 0.8) * zIndex; + glDepthRange(0.1 - offset, 1.0 - offset); + break; + } +} + +static void ssglFillPattern(Canvas::FillPattern pattern) { + static bool Init; + static GLubyte MaskA[(32*32)/8]; + static GLubyte MaskB[(32*32)/8]; + 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 ? MaskB : MaskA)[i] |= (0x80 >> b); + } + ym = (ym + 2) % 4; xm = (xm + 2) % 4; + } + } + } + Init = true; + } + + switch(pattern) { + case Canvas::FillPattern::SOLID: + glDisable(GL_POLYGON_STIPPLE); + break; + + case Canvas::FillPattern::CHECKERED_A: + glEnable(GL_POLYGON_STIPPLE); + glPolygonStipple(MaskA); + break; + + case Canvas::FillPattern::CHECKERED_B: + glEnable(GL_POLYGON_STIPPLE); + glPolygonStipple(MaskB); + break; + } +} + +//----------------------------------------------------------------------------- +// A simple OpenGL state tracker to group consecutive draw calls. +//----------------------------------------------------------------------------- + +void OpenGl1Renderer::SelectPrimitive(GLenum mode) { + if(current.drawing && current.mode == mode) { + return; + } else if(current.drawing) { + glEnd(); + } + glBegin(mode); + current.drawing = true; + current.mode = mode; +} + +void OpenGl1Renderer::UnSelectPrimitive() { + if(!current.drawing) return; + glEnd(); + current.drawing = false; +} + +Canvas::Stroke *OpenGl1Renderer::SelectStroke(hStroke hcs) { + if(current.hcs.v == hcs.v) return current.stroke; + + Stroke *stroke = strokes.FindById(hcs); + UnSelectPrimitive(); + ssglColorRGBA(stroke->color); + ssglDepthRange(stroke->layer, stroke->zIndex); + ssglLineWidth(stroke->width); + // Fat lines and points are quads affected by glPolygonStipple, so make sure + // they are displayed correctly. + ssglFillPattern(FillPattern::SOLID); + glDisable(GL_TEXTURE_2D); + + current.hcs = hcs; + current.stroke = stroke; + current.hcf = {}; + current.fill = NULL; + current.texture.reset(); + return stroke; +} + +Canvas::Fill *OpenGl1Renderer::SelectFill(hFill hcf) { + if(current.hcf.v == hcf.v) return current.fill; + + Fill *fill = fills.FindById(hcf); + UnSelectPrimitive(); + ssglColorRGBA(fill->color); + ssglDepthRange(fill->layer, fill->zIndex); + ssglFillPattern(fill->pattern); + glDisable(GL_TEXTURE_2D); + + current.hcs = {}; + current.stroke = NULL; + current.hcf = hcf; + current.fill = fill; + current.texture.reset(); + return fill; +} + +void OpenGl1Renderer::SelectTexture(std::shared_ptr pm) { + if(current.texture.lock() == pm) return; + + glBindTexture(GL_TEXTURE_2D, 1); + 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); + + GLenum format; + switch(pm->format) { + case Pixmap::Format::RGBA: format = GL_RGBA; break; + case Pixmap::Format::RGB: format = GL_RGB; break; + case Pixmap::Format::A: format = GL_ALPHA; break; + } + glTexImage2D(GL_TEXTURE_2D, 0, format, pm->width, pm->height, 0, + format, GL_UNSIGNED_BYTE, &pm->data[0]); + glEnable(GL_TEXTURE_2D); + + current.texture = pm; +} + +//----------------------------------------------------------------------------- +// OpenGL's GL_LINES mode does not work on lines thicker than about 3 px, +// so we have to draw thicker lines using triangles. +//----------------------------------------------------------------------------- + +void OpenGl1Renderer::DoFatLineEndcap(const Vector &p, const Vector &u, const 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 }, + }; + + SelectPrimitive(GL_TRIANGLE_FAN); + for(auto pc : Circle) { + double c = pc[0], s = pc[1]; + ssglVertex3v(p.Plus(u.ScaledBy(c)).Plus(v.ScaledBy(s))); + } + UnSelectPrimitive(); +} + +void OpenGl1Renderer::DoFatLine(const Vector &a, const Vector &b, double width) { + // The half-width of the line we're drawing. + double hw = width / 2; + Vector ab = b.Minus(a); + Vector gn = (camera.projRight).Cross(camera.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 + SelectPrimitive(GL_QUADS); + ssglVertex3v(a.Plus (abn)); + ssglVertex3v(b.Plus (abn)); + ssglVertex3v(b.Minus(abn)); + ssglVertex3v(a.Minus(abn)); + // And the line has two semi-circular end caps. + DoFatLineEndcap(a, ab, abn); + DoFatLineEndcap(b, ab.ScaledBy(-1), abn); +} + +void OpenGl1Renderer::DoLine(const Vector &a, const Vector &b, hStroke hcs) { + if(a.Equals(b)) return; + + Stroke *stroke = SelectStroke(hcs); + if(stroke->width <= 3.0) { + SelectPrimitive(GL_LINES); + ssglVertex3v(a); + ssglVertex3v(b); + } else { + DoFatLine(a, b, stroke->width / camera.scale); + } +} + +void OpenGl1Renderer::DoPoint(Vector p, double d) { + if(d <= 3.0) { + Vector u = camera.projRight.WithMagnitude(d / 2.0 / camera.scale); + + SelectPrimitive(GL_LINES); + ssglVertex3v(p.Minus(u)); + ssglVertex3v(p.Plus(u)); + } else { + Vector u = camera.projRight.WithMagnitude(d / 2.0 / camera.scale); + Vector v = camera.projUp.WithMagnitude(d / 2.0 / camera.scale); + + DoFatLineEndcap(p, u, v); + DoFatLineEndcap(p, u.ScaledBy(-1.0), v); + } +} + +void OpenGl1Renderer::DoStippledLine(const Vector &a, const Vector &b, hStroke hcs) { + Stroke *stroke = SelectStroke(hcs); + + const char *patternSeq; + switch(stroke->stipplePattern) { + case StipplePattern::CONTINUOUS: DoLine(a, b, hcs); return; + case StipplePattern::SHORT_DASH: patternSeq = "- "; break; + case StipplePattern::DASH: patternSeq = "- "; break; + case StipplePattern::LONG_DASH: patternSeq = "_ "; break; + case StipplePattern::DASH_DOT: patternSeq = "-."; break; + case StipplePattern::DASH_DOT_DOT: patternSeq = "-.."; break; + case StipplePattern::DOT: patternSeq = "."; break; + case StipplePattern::FREEHAND: patternSeq = "~"; break; + case StipplePattern::ZIGZAG: patternSeq = "~__"; break; + } + + Vector dir = b.Minus(a); + double len = dir.Magnitude(); + dir = dir.WithMagnitude(1.0); + + const char *si = patternSeq; + double end = len; + double ss = stroke->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; + DoLine(a.Plus(dir.ScaledBy(start)), a.Plus(dir.ScaledBy(end)), hcs); + end = max(end - 0.5 * ss, 0.0); + break; + + case '_': + end = max(end - 4.0 * ss, 0.0); + DoLine(a.Plus(dir.ScaledBy(start)), a.Plus(dir.ScaledBy(end)), hcs); + break; + + case '.': + end = max(end - 0.5 * ss, 0.0); + if(end == 0.0) break; + DoPoint(a.Plus(dir.ScaledBy(end)), stroke->width); + end = max(end - 0.5 * ss, 0.0); + break; + + case '~': { + Vector ab = b.Minus(a); + Vector gn = (camera.projRight).Cross(camera.projUp); + Vector abn = (ab.Cross(gn)).WithMagnitude(1); + abn = abn.Minus(gn.ScaledBy(gn.Dot(abn))); + double pws = 2.0 * stroke->width / camera.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))); + DoLine(aa, bb, hcs); + 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)); + DoLine(bb, aa, hcs); + 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))); + DoLine(aa, bb, hcs); + break; + } + + default: ssassert(false, "Unexpected stipple pattern element"); + } + if(*(++si) == 0) si = patternSeq; + } while(end > 0.0); +} + +//----------------------------------------------------------------------------- +// A canvas implemented using OpenGL 2 immediate mode. +//----------------------------------------------------------------------------- + +void OpenGl1Renderer::DrawLine(const Vector &a, const Vector &b, hStroke hcs) { + DoStippledLine(a, b, hcs); +} + +void OpenGl1Renderer::DrawEdges(const SEdgeList &el, hStroke hcs) { + for(const SEdge *e = el.l.First(); e; e = el.l.NextAfter(e)) { + DoStippledLine(e->a, e->b, hcs); + } +} + +void OpenGl1Renderer::DrawOutlines(const SOutlineList &ol, hStroke hcs) { + Vector projDir = camera.projRight.Cross(camera.projUp); + for(const SOutline *o = ol.l.First(); o; o = ol.l.NextAfter(o)) { + if(!o->IsVisible(projDir)) continue; + DoStippledLine(o->a, o->b, hcs); + } +} + +void OpenGl1Renderer::DrawVectorText(const std::string &text, double height, + const Vector &o, const Vector &u, const Vector &v, + hStroke hcs) { + auto traceEdge = [&](Vector a, Vector b) { DoStippledLine(a, b, hcs); }; + VectorFont::Builtin()->Trace(height, o, u, v, text, traceEdge, camera); +} + +void OpenGl1Renderer::DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d, + hFill hcf) { + SelectFill(hcf); + SelectPrimitive(GL_QUADS); + ssglVertex3v(a); + ssglVertex3v(b); + ssglVertex3v(c); + ssglVertex3v(d); +} + +void OpenGl1Renderer::DrawPoint(const Vector &o, double s, hFill hcf) { + Vector r = camera.projRight.ScaledBy(s/camera.scale); + Vector u = camera.projUp.ScaledBy(s/camera.scale); + Vector a = o.Plus (r).Plus (u), + b = o.Plus (r).Minus(u), + c = o.Minus(r).Minus(u), + d = o.Minus(r).Plus (u); + DrawQuad(a, b, c, d, hcf); +} + +#ifdef WIN32 +#define SSGL_CALLBACK CALLBACK +#else +#define SSGL_CALLBACK +#endif +typedef void(SSGL_CALLBACK *GLUCallback)(); + +static void SSGL_CALLBACK Vertex(Vector *p) { + ssglVertex3v(*p); +} +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 OpenGl1Renderer::DrawPolygon(const SPolygon &p, hFill hcf) { + UnSelectPrimitive(); + SelectFill(hcf); + + GLUtesselator *gt = gluNewTess(); + gluTessCallback(gt, GLU_TESS_BEGIN, (GLUCallback) glBegin); + gluTessCallback(gt, GLU_TESS_VERTEX, (GLUCallback) Vertex); + gluTessCallback(gt, GLU_TESS_END, (GLUCallback) glEnd); + gluTessCallback(gt, GLU_TESS_COMBINE, (GLUCallback) Combine); + + gluTessProperty(gt, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_ODD); + + ssglNormal3v(p.normal); + gluTessNormal(gt, p.normal.x, p.normal.y, p.normal.z); + + gluTessBeginPolygon(gt, NULL); + for(const SContour &sc : p.l) { + gluTessBeginContour(gt); + for(const SPoint &sp : sc.l) { + double ap[3] = { sp.p.x, sp.p.y, sp.p.z }; + gluTessVertex(gt, ap, (GLvoid *) &sp.p); + } + gluTessEndContour(gt); + } + gluTessEndPolygon(gt); + + gluDeleteTess(gt); +} + +void OpenGl1Renderer::DrawMesh(const SMesh &m, + hFill hcfFront, hFill hcfBack, hStroke hcsTriangles) { + UnSelectPrimitive(); + + Fill *frontFill = SelectFill(hcfFront); + ssglMaterialRGBA(GL_FRONT, frontFill->color); + + if(hcfBack.v != 0) { + Fill *backFill = fills.FindById(hcfBack); + ssassert(frontFill->layer == backFill->layer && + frontFill->zIndex == backFill->zIndex, + "frontFill and backFill should belong to the same depth range"); + ssassert(frontFill->pattern == backFill->pattern, + "frontFill and backFill should have the same pattern"); + ssglMaterialRGBA(GL_BACK, backFill->color); + glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 1); + } else { + glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 0); + } + + RgbaColor frontColor = {}; + glEnable(GL_LIGHTING); + glBegin(GL_TRIANGLES); + for(const STriangle &tr : m.l) { + if(frontFill->color.IsEmpty()) { + if(frontColor.IsEmpty() || !frontColor.Equals(tr.meta.color)) { + frontColor = tr.meta.color; + ssglMaterialRGBA(GL_FRONT, frontColor); + } + } + + if(tr.an.EqualsExactly(Vector::From(0, 0, 0))) { + // Compute the normal from the vertices + ssglNormal3v(tr.Normal()); + ssglVertex3v(tr.a); + ssglVertex3v(tr.b); + ssglVertex3v(tr.c); + } else { + // Use the exact normals that are specified + ssglNormal3v(tr.an); + ssglVertex3v(tr.a); + ssglNormal3v(tr.bn); + ssglVertex3v(tr.b); + ssglNormal3v(tr.cn); + ssglVertex3v(tr.c); + } + } + glEnd(); + glDisable(GL_LIGHTING); + + if(hcsTriangles.v != 0) { + Stroke *triangleStroke = SelectStroke(hcsTriangles); + ssassert(triangleStroke->width == 1 && + triangleStroke->stipplePattern == StipplePattern::CONTINUOUS && + triangleStroke->stippleScale == 0.0, + "Triangle stroke must match predefined OpenGL parameters"); + + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + glBegin(GL_TRIANGLES); + for(const STriangle &tr : m.l) { + ssglVertex3v(tr.a); + ssglVertex3v(tr.b); + ssglVertex3v(tr.c); + } + glEnd(); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + } +} + +void OpenGl1Renderer::DrawFaces(const SMesh &m, const std::vector &faces, hFill hcf) { + SelectFill(hcf); + SelectPrimitive(GL_TRIANGLES); + size_t facesSize = faces.size(); + for(const STriangle &tr : m.l) { + uint32_t face = tr.meta.face; + for(size_t j = 0; j < facesSize; j++) { + if(faces[j] != face) continue; + ssglVertex3v(tr.a); + ssglVertex3v(tr.b); + ssglVertex3v(tr.c); + break; + } + } +} + +void OpenGl1Renderer::DrawPixmap(std::shared_ptr pm, + const Vector &o, const Vector &u, const Vector &v, + const Point2d &ta, const Point2d &tb, hFill hcf) { + UnSelectPrimitive(); + SelectFill(hcf); + SelectTexture(pm); + SelectPrimitive(GL_QUADS); + glTexCoord2d(ta.x, ta.y); + ssglVertex3v(o); + glTexCoord2d(ta.x, tb.y); + ssglVertex3v(o.Plus(v)); + glTexCoord2d(tb.x, tb.y); + ssglVertex3v(o.Plus(u).Plus(v)); + glTexCoord2d(tb.x, ta.y); + ssglVertex3v(o.Plus(u)); +} + +void OpenGl1Renderer::InvalidatePixmap(std::shared_ptr pm) { + if(current.texture.lock() == pm) { + current.texture.reset(); + } +} + +void OpenGl1Renderer::UpdateProjection(bool flip) { + UnSelectPrimitive(); + + glViewport(0, 0, camera.width, camera.height); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + glScaled(camera.scale * 2.0 / camera.width, + camera.scale * 2.0 / camera.height, + camera.scale * 1.0 / 30000); + + double mat[16]; + // Last thing before display is to apply the perspective + double clp = camera.tangent * camera.scale; + double sy = flip ? -1.0 : 1.0; + MakeMatrix(mat, 1, 0, 0, 0, + 0, sy, 0, 0, + 0, 0, 1, 0, + 0, 0, clp, 1); + glMultMatrixd(mat); + // Before that, we apply the rotation + Vector projRight = camera.projRight, + projUp = camera.projUp, + n = camera.projUp.Cross(camera.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 + Vector offset = camera.offset; + MakeMatrix(mat, 1, 0, 0, offset.x, + 0, 1, 0, offset.y, + 0, 0, 1, offset.z, + 0, 0, 0, 1); + glMultMatrixd(mat); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); +} + +void OpenGl1Renderer::BeginFrame() { + glEnable(GL_NORMALIZE); + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_BLEND); + + glEnable(GL_LINE_SMOOTH); + glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); + // don't enable GL_POLYGON_SMOOTH; that looks ugly on some graphics cards, + // drawn with leaks in the mesh + + glDepthFunc(GL_LEQUAL); + glEnable(GL_DEPTH_TEST); + + if(EXACT(lighting.lightIntensity[0] != 0.0)) { + glEnable(GL_LIGHT0); + GLfloat f = (GLfloat)lighting.lightIntensity[0]; + GLfloat li0[] = { f, f, f, 1.0f }; + glLightfv(GL_LIGHT0, GL_DIFFUSE, li0); + glLightfv(GL_LIGHT0, GL_SPECULAR, li0); + + Vector ld = camera.VectorFromProjs(lighting.lightDirection[0]); + GLfloat ld0[4] = { (GLfloat)ld.x, (GLfloat)ld.y, (GLfloat)ld.z, 0 }; + glLightfv(GL_LIGHT0, GL_POSITION, ld0); + } + + if(EXACT(lighting.lightIntensity[1] != 0.0)) { + glEnable(GL_LIGHT1); + GLfloat f = (GLfloat)lighting.lightIntensity[1]; + GLfloat li0[] = { f, f, f, 1.0f }; + glLightfv(GL_LIGHT1, GL_DIFFUSE, li0); + glLightfv(GL_LIGHT1, GL_SPECULAR, li0); + + Vector ld = camera.VectorFromProjs(lighting.lightDirection[1]); + GLfloat ld0[4] = { (GLfloat)ld.x, (GLfloat)ld.y, (GLfloat)ld.z, 0 }; + glLightfv(GL_LIGHT1, GL_POSITION, ld0); + } + + if(EXACT(lighting.ambientIntensity != 0.0)) { + GLfloat ambient[4] = { (float)lighting.ambientIntensity, + (float)lighting.ambientIntensity, + (float)lighting.ambientIntensity, 1 }; + glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient); + } + + glClearColor(lighting.backgroundColor.redF(), lighting.backgroundColor.greenF(), + lighting.backgroundColor.blueF(), lighting.backgroundColor.alphaF()); + glClear(GL_COLOR_BUFFER_BIT); + + glClearDepth(1.0); + glClear(GL_DEPTH_BUFFER_BIT); +} + +void OpenGl1Renderer::EndFrame() { + UnSelectPrimitive(); + glFlush(); + + GLenum error = glGetError(); + if(error != GL_NO_ERROR) { + dbp("glGetError() == 0x%X %s", error, gluErrorString(error)); + } +} + +std::shared_ptr OpenGl1Renderer::ReadFrame() { + std::shared_ptr pixmap = + Pixmap::Create(Pixmap::Format::RGB, (size_t)camera.width, (size_t)camera.height); + glReadPixels(0, 0, camera.width, camera.height, GL_RGB, GL_UNSIGNED_BYTE, &pixmap->data[0]); + return pixmap; +} + +void OpenGl1Renderer::GetIdent(const char **vendor, const char **renderer, const char **version) { + *vendor = (const char *)glGetString(GL_VENDOR); + *renderer = (const char *)glGetString(GL_RENDERER); + *version = (const char *)glGetString(GL_VERSION); +} + +} diff --git a/src/resource.cpp b/src/resource.cpp index 5f590c18..6dbcd393 100644 --- a/src/resource.cpp +++ b/src/resource.cpp @@ -48,12 +48,12 @@ std::string LoadStringFromGzip(const std::string &name) { return result; } -Pixmap LoadPNG(const std::string &name) { +std::shared_ptr LoadPng(const std::string &name) { size_t size; const void *data = LoadResource(name, &size); - Pixmap pixmap = Pixmap::FromPNG(static_cast(data), size); - ssassert(!pixmap.IsEmpty(), "Cannot load pixmap"); + std::shared_ptr pixmap = Pixmap::FromPng(static_cast(data), size); + ssassert(pixmap != nullptr, "Cannot load pixmap"); return pixmap; } @@ -62,44 +62,59 @@ Pixmap LoadPNG(const std::string &name) { // Pixmap manipulation //----------------------------------------------------------------------------- -void Pixmap::Clear() { - *this = {}; +size_t Pixmap::GetBytesPerPixel() const { + switch(format) { + case Format::RGBA: return 4; + case Format::RGB: return 3; + case Format::A: return 1; + } + ssassert(false, "Unexpected pixmap format"); } RgbaColor Pixmap::GetPixel(size_t x, size_t y) const { const uint8_t *pixel = &data[y * stride + x * GetBytesPerPixel()]; - if(hasAlpha) { - return RgbaColor::From(pixel[0], pixel[1], pixel[2], pixel[3]); - } else { - return RgbaColor::From(pixel[0], pixel[1], pixel[2]); + switch(format) { + case Format::RGBA: + return RgbaColor::From(pixel[0], pixel[1], pixel[2], pixel[3]); + + case Format::RGB: + return RgbaColor::From(pixel[0], pixel[1], pixel[2], 255); + + case Format::A: + return RgbaColor::From( 255, 255, 255, pixel[0]); } + ssassert(false, "Unexpected resource format"); } -static Pixmap ReadPNGIntoPixmap(png_struct *png_ptr, png_info *info_ptr) { +static std::shared_ptr ReadPngIntoPixmap(png_struct *png_ptr, png_info *info_ptr) { png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_EXPAND | PNG_TRANSFORM_GRAY_TO_RGB, NULL); - Pixmap pixmap = {}; - pixmap.width = png_get_image_width(png_ptr, info_ptr); - pixmap.height = png_get_image_height(png_ptr, info_ptr); - pixmap.hasAlpha = (png_get_color_type(png_ptr, info_ptr) & PNG_COLOR_MASK_ALPHA) != 0; - - size_t stride = pixmap.width * pixmap.GetBytesPerPixel(); - if(stride % 4 != 0) stride += 4 - stride % 4; - pixmap.stride = stride; - - pixmap.data = std::vector(pixmap.stride * pixmap.height); - uint8_t **rows = png_get_rows(png_ptr, info_ptr); - for(size_t y = 0; y < pixmap.height; y++) { - memcpy(&pixmap.data[pixmap.stride * y], rows[y], - pixmap.width * pixmap.GetBytesPerPixel()); + std::shared_ptr pixmap = std::make_shared(); + pixmap->width = png_get_image_width(png_ptr, info_ptr); + pixmap->height = png_get_image_height(png_ptr, info_ptr); + if((png_get_color_type(png_ptr, info_ptr) & PNG_COLOR_MASK_ALPHA) != 0) { + pixmap->format = Pixmap::Format::RGBA; + } else { + pixmap->format = Pixmap::Format::RGB; } + size_t stride = pixmap->width * pixmap->GetBytesPerPixel(); + if(stride % 4 != 0) stride += 4 - stride % 4; + pixmap->stride = stride; + + pixmap->data = std::vector(pixmap->stride * pixmap->height); + uint8_t **rows = png_get_rows(png_ptr, info_ptr); + for(size_t y = 0; y < pixmap->height; y++) { + memcpy(&pixmap->data[pixmap->stride * y], rows[y], + pixmap->width * pixmap->GetBytesPerPixel()); + } + + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); return pixmap; } -Pixmap Pixmap::FromPNG(const uint8_t *data, size_t size) { - Pixmap pixmap = {}; +std::shared_ptr Pixmap::FromPng(const uint8_t *data, size_t size) { struct Slice { const uint8_t *data; size_t size; }; Slice dataSlice = { data, size }; png_struct *png_ptr = NULL; @@ -124,16 +139,14 @@ Pixmap Pixmap::FromPNG(const uint8_t *data, size_t size) { } }); - pixmap = ReadPNGIntoPixmap(png_ptr, info_ptr); + return ReadPngIntoPixmap(png_ptr, info_ptr); exit: png_destroy_read_struct(&png_ptr, &info_ptr, NULL); - return pixmap; + return nullptr; } -Pixmap Pixmap::FromPNG(FILE *f) { - Pixmap pixmap = {}; - +std::shared_ptr Pixmap::ReadPng(FILE *f) { png_struct *png_ptr = NULL; png_info *info_ptr = NULL; @@ -151,10 +164,66 @@ Pixmap Pixmap::FromPNG(FILE *f) { png_init_io(png_ptr, f); png_set_sig_bytes(png_ptr, sizeof(header)); - pixmap = ReadPNGIntoPixmap(png_ptr, info_ptr); + return ReadPngIntoPixmap(png_ptr, info_ptr); exit: png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + return nullptr; +} + +bool Pixmap::WritePng(FILE *f, bool flip) { + int colorType; + switch(format) { + case Format::RGBA: colorType = PNG_COLOR_TYPE_RGBA; break; + case Format::RGB: colorType = PNG_COLOR_TYPE_RGB; break; + case Format::A: ssassert(false, "Unexpected pixmap format"); + } + + std::vector rows; + for(size_t y = 0; y < height; y++) { + if(flip) { + rows.push_back(&data[stride * y]); + } else { + rows.push_back(&data[stride * (height - y)]); + } + } + + png_struct *png_ptr = NULL; + png_info *info_ptr = NULL; + + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if(!png_ptr) goto exit; + info_ptr = png_create_info_struct(png_ptr); + if(!info_ptr) goto exit; + + if(setjmp(png_jmpbuf(png_ptr))) goto exit; + + png_init_io(png_ptr, f); + png_set_IHDR(png_ptr, info_ptr, width, height, 8, + colorType, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + png_write_info(png_ptr, info_ptr); + png_write_image(png_ptr, &rows[0]); + png_write_end(png_ptr, info_ptr); + + png_destroy_write_struct(&png_ptr, &info_ptr); + return true; + +exit: + png_destroy_write_struct(&png_ptr, &info_ptr); + return false; +} + +std::shared_ptr Pixmap::Create(Format format, size_t width, size_t height) { + std::shared_ptr pixmap = std::make_shared(); + pixmap->format = format; + pixmap->width = width; + pixmap->height = height; + // Align to fulfill OpenGL texture requirements. + size_t stride = pixmap->width * pixmap->GetBytesPerPixel(); + if(stride % 4 != 0) stride += 4 - stride % 4; + pixmap->stride = stride; + pixmap->data = std::vector(pixmap->stride * pixmap->height); return pixmap; } @@ -246,39 +315,41 @@ public: // Bitmap font manipulation //----------------------------------------------------------------------------- -static const size_t CHARS_PER_ROW = BitmapFont::TEXTURE_DIM / 16; - -static uint8_t *BitmapFontTextureRow(uint8_t *texture, uint16_t position, size_t y) { +static uint8_t *BitmapFontTextureRow(std::shared_ptr texture, + uint16_t position, size_t y) { // position = 0; - size_t col = position % CHARS_PER_ROW, - row = position / CHARS_PER_ROW; - return &texture[BitmapFont::TEXTURE_DIM * (16 * row + y) + 16 * col]; + size_t col = position % (texture->width / 16), + row = position / (texture->width / 16); + return &texture->data[texture->stride * (16 * row + y) + 16 * col]; } BitmapFont BitmapFont::From(std::string &&unifontData) { BitmapFont font = {}; font.unifontData = std::move(unifontData); - font.texture = std::vector(TEXTURE_DIM * TEXTURE_DIM); + font.texture = Pixmap::Create(Pixmap::Format::A, 1024, 1024); + return font; } -void BitmapFont::AddGlyph(char32_t codepoint, const Pixmap &pixmap) { - ssassert((pixmap.width == 8 || pixmap.width == 16) && pixmap.height == 16, - "Unexpected glyph dimensions"); +void BitmapFont::AddGlyph(char32_t codepoint, std::shared_ptr pixmap) { + ssassert((pixmap->width == 8 || pixmap->width == 16) && pixmap->height == 16, + "Unexpected pixmap dimensions"); + ssassert(pixmap->format == Pixmap::Format::RGB, + "Unexpected pixmap format"); ssassert(glyphs.find(codepoint) == glyphs.end(), "Glyph with this codepoint already exists"); ssassert(nextPosition != 0xffff, "Too many glyphs for current texture size"); BitmapFont::Glyph glyph = {}; - glyph.advanceCells = pixmap.width / 8; + glyph.advanceCells = pixmap->width / 8; glyph.position = nextPosition++; glyphs.emplace(codepoint, std::move(glyph)); - for(size_t y = 0; y < pixmap.height; y++) { - uint8_t *row = BitmapFontTextureRow(&texture[0], glyph.position, y); - for(size_t x = 0; x < pixmap.width; x++) { - if(pixmap.GetPixel(x, y).ToPackedInt() != 0) { + for(size_t y = 0; y < pixmap->height; y++) { + uint8_t *row = BitmapFontTextureRow(texture, glyph.position, y); + for(size_t x = 0; x < pixmap->width; x++) { + if((pixmap->GetPixel(x, y).ToPackedInt() & 0xffffff) != 0) { row[x] = 255; } } @@ -346,7 +417,7 @@ const BitmapFont::Glyph &BitmapFont::GetGlyph(char32_t codepoint) { // Fill in the texture (one texture byte per glyph bit). for(size_t y = 0; y < 16; y++) { - uint8_t *row = BitmapFontTextureRow(&texture[0], glyph.position, y); + uint8_t *row = BitmapFontTextureRow(texture, glyph.position, y); for(size_t x = 0; x < 16; x++) { if(glyphBits[y] & (1 << (15 - x))) { row[x] = 255; @@ -370,13 +441,49 @@ bool BitmapFont::LocateGlyph(char32_t codepoint, const Glyph &glyph = GetGlyph(codepoint); *w = glyph.advanceCells * 8; *h = 16; - *s0 = (16.0 * (glyph.position % CHARS_PER_ROW)) / TEXTURE_DIM; - *s1 = *s0 + (double)(*w) / TEXTURE_DIM; - *t0 = (16.0 * (glyph.position / CHARS_PER_ROW)) / TEXTURE_DIM; - *t1 = *t0 + (double)(*h) / TEXTURE_DIM; + *s0 = (16.0 * (glyph.position % (texture->width / 16))) / texture->width; + *s1 = *s0 + (double)(*w) / texture->width; + *t0 = (16.0 * (glyph.position / (texture->width / 16))) / texture->height; + *t1 = *t0 + (double)(*h) / texture->height; return textureUpdated; } +size_t BitmapFont::GetWidth(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; + } + + return GetGlyph(codepoint).advanceCells; +} + +size_t BitmapFont::GetWidth(const std::string &str) { + size_t width = 0; + for(char32_t codepoint : ReadUTF8(str)) { + width += GetWidth(codepoint); + } + return width; +} + +BitmapFont *BitmapFont::Builtin() { + static BitmapFont Font; + if(Font.IsEmpty()) { + Font = BitmapFont::From(LoadStringFromGzip("fonts/unifont.hex.gz")); + // Unifont doesn't have a glyph for U+0020. + Font.AddGlyph(0x0020, Pixmap::Create(Pixmap::Format::RGB, 8, 16)); + Font.AddGlyph(0xE000, LoadPng("fonts/private/0-check-false.png")); + Font.AddGlyph(0xE001, LoadPng("fonts/private/1-check-true.png")); + Font.AddGlyph(0xE002, LoadPng("fonts/private/2-radio-false.png")); + Font.AddGlyph(0xE003, LoadPng("fonts/private/3-radio-true.png")); + Font.AddGlyph(0xE004, LoadPng("fonts/private/4-stipple-dot.png")); + Font.AddGlyph(0xE005, LoadPng("fonts/private/5-stipple-dash-long.png")); + Font.AddGlyph(0xE006, LoadPng("fonts/private/6-stipple-dash.png")); + Font.AddGlyph(0xE007, LoadPng("fonts/private/7-stipple-zigzag.png")); + } + return &Font; +} + //----------------------------------------------------------------------------- // Vector font manipulation //----------------------------------------------------------------------------- @@ -476,6 +583,7 @@ VectorFont VectorFont::From(std::string &&lffData) { GetGlyphBBox(font.GetGlyph('h'), nullptr, nullptr, nullptr, &font.ascender); GetGlyphBBox(font.GetGlyph('p'), nullptr, nullptr, &font.descender, nullptr); + ssassert(!font.IsEmpty(), "Expected to load a font"); return font; } @@ -574,4 +682,115 @@ const VectorFont::Glyph &VectorFont::GetGlyph(char32_t codepoint) { return GetGlyph(0xfffd); } +VectorFont *VectorFont::Builtin() { + static VectorFont Font; + if(Font.IsEmpty()) { + Font = VectorFont::From(LoadStringFromGzip("fonts/unicode.lff.gz")); + } + return &Font; +} + +double VectorFont::GetCapHeight(double forCapHeight) const { + ssassert(!IsEmpty(), "Expected a loaded font"); + + return forCapHeight; +} + +double VectorFont::GetHeight(double forCapHeight) const { + ssassert(!IsEmpty(), "Expected a loaded font"); + + return (ascender - descender) * (forCapHeight / capHeight); +} + +double VectorFont::GetWidth(double forCapHeight, const std::string &str) { + ssassert(!IsEmpty(), "Expected a loaded font"); + + double width = 0; + for(char32_t codepoint : ReadUTF8(str)) { + width += GetGlyph(codepoint).advanceWidth; + } + width -= rightSideBearing; + return width * (forCapHeight / capHeight); +} + +Vector VectorFont::GetExtents(double forCapHeight, const std::string &str) { + Vector ex = {}; + ex.x = GetWidth(forCapHeight, str); + ex.y = GetHeight(forCapHeight); + return ex; +} + +void VectorFont::Trace(double forCapHeight, Vector o, Vector u, Vector v, const std::string &str, + std::function traceEdge) { + ssassert(!IsEmpty(), "Expected a loaded font"); + + double scale = (forCapHeight / capHeight); + u = u.ScaledBy(scale); + v = v.ScaledBy(scale); + + for(char32_t codepoint : ReadUTF8(str)) { + const Glyph &glyph = GetGlyph(codepoint); + + for(const VectorFont::Contour &contour : glyph.contours) { + Vector prevp; + bool penUp = true; + for(const Point2d &pt : contour.points) { + Vector p = o.Plus(u.ScaledBy(pt.x)) + .Plus(v.ScaledBy(pt.y)); + if(!penUp) traceEdge(prevp, p); + prevp = p; + penUp = false; + } + } + + o = o.Plus(u.ScaledBy(glyph.advanceWidth)); + } +} + +void VectorFont::Trace(double forCapHeight, Vector o, Vector u, Vector v, const std::string &str, + std::function traceEdge, const Camera &camera) { + ssassert(!IsEmpty(), "Expected a loaded font"); + + // Perform grid-fitting only when the text is parallel to the view plane. + if(camera.hasPixels && !(u.WithMagnitude(1).Equals(camera.projRight) && + v.WithMagnitude(1).Equals(camera.projUp))) { + return Trace(forCapHeight, o, u, v, str, traceEdge); + } + + double scale = forCapHeight / capHeight; + u = u.ScaledBy(scale); + v = v.ScaledBy(scale); + + for(char32_t codepoint : ReadUTF8(str)) { + const Glyph &glyph = GetGlyph(codepoint); + double actualWidth = std::max(1.0, glyph.boundingWidth); + + // Align (o+lsb), (o+lsb+u) and (o+lsb+v) to pixel grid. + Vector ao = o.Plus(u.ScaledBy(glyph.leftSideBearing)); + Vector au = ao.Plus(u.ScaledBy(actualWidth)); + Vector av = ao.Plus(v.ScaledBy(capHeight)); + + ao = camera.AlignToPixelGrid(ao); + au = camera.AlignToPixelGrid(au); + av = camera.AlignToPixelGrid(av); + + au = au.Minus(ao).ScaledBy(1.0 / actualWidth); + av = av.Minus(ao).ScaledBy(1.0 / capHeight); + + for(const VectorFont::Contour &contour : glyph.contours) { + Vector prevp; + bool penUp = true; + for(const Point2d &pt : contour.points) { + Vector p = ao.Plus(au.ScaledBy(pt.x - glyph.leftSideBearing)) + .Plus(av.ScaledBy(pt.y)); + if(!penUp) traceEdge(prevp, p); + prevp = p; + penUp = false; + } + } + + o = o.Plus(u.ScaledBy(glyph.advanceWidth)); + } +} + } diff --git a/src/resource.h b/src/resource.h index 68d97b34..3249f074 100644 --- a/src/resource.h +++ b/src/resource.h @@ -7,8 +7,10 @@ #ifndef __RESOURCE_H #define __RESOURCE_H +class Camera; class Point2d; class Pixmap; +class Vector; // Only the following function is platform-specific. // It returns a pointer to resource contents that is aligned to at least @@ -18,24 +20,26 @@ const void *LoadResource(const std::string &name, size_t *size); std::string LoadString(const std::string &name); std::string LoadStringFromGzip(const std::string &name); -Pixmap LoadPNG(const std::string &name); +std::shared_ptr LoadPng(const std::string &name); class Pixmap { public: + enum class Format { RGBA, RGB, A }; + + Format format; size_t width; size_t height; size_t stride; - bool hasAlpha; std::vector data; - static Pixmap FromPNG(const uint8_t *data, size_t size); - static Pixmap FromPNG(FILE *f); + static std::shared_ptr Create(Format format, size_t width, size_t height); + static std::shared_ptr FromPng(const uint8_t *data, size_t size); - bool IsEmpty() const { return width == 0 && height == 0; } - size_t GetBytesPerPixel() const { return hasAlpha ? 4 : 3; } + static std::shared_ptr ReadPng(FILE *f); + bool WritePng(FILE *f, bool flip = false); + + size_t GetBytesPerPixel() const; RgbaColor GetPixel(size_t x, size_t y) const; - - void Clear(); }; class BitmapFont { @@ -45,21 +49,23 @@ public: uint16_t position; }; - static const size_t TEXTURE_DIM = 1024; - std::string unifontData; std::map glyphs; - std::vector texture; + std::shared_ptr texture; uint16_t nextPosition; static BitmapFont From(std::string &&unifontData); + static BitmapFont *Builtin(); bool IsEmpty() const { return unifontData.empty(); } const Glyph &GetGlyph(char32_t codepoint); bool LocateGlyph(char32_t codepoint, double *s0, double *t0, double *s1, double *t1, size_t *advanceWidth, size_t *boundingHeight); - void AddGlyph(char32_t codepoint, const Pixmap &pixmap); + void AddGlyph(char32_t codepoint, std::shared_ptr pixmap); + + size_t GetWidth(char32_t codepoint); + size_t GetWidth(const std::string &str); }; class VectorFont { @@ -83,10 +89,20 @@ public: double descender; static VectorFont From(std::string &&lffData); + static VectorFont *Builtin(); bool IsEmpty() const { return lffData.empty(); } - const Glyph &GetGlyph(char32_t codepoint); + + double GetCapHeight(double forCapHeight) const; + double GetHeight(double forCapHeight) const; + double GetWidth(double forCapHeight, const std::string &str); + Vector GetExtents(double forCapHeight, const std::string &str); + + void Trace(double forCapHeight, Vector o, Vector u, Vector v, const std::string &str, + std::function traceEdge); + void Trace(double forCapHeight, Vector o, Vector u, Vector v, const std::string &str, + std::function traceEdge, const Camera &camera); }; #endif diff --git a/src/sketch.h b/src/sketch.h index 0aabe67f..c9ec9705 100644 --- a/src/sketch.h +++ b/src/sketch.h @@ -8,11 +8,6 @@ #ifndef __SKETCH_H #define __SKETCH_H -#ifdef WIN32 - // winuser.h confilct (DIFFERENCE) - #undef DIFFERENCE -#endif - class hGroup; class hRequest; class hEntity; @@ -270,16 +265,17 @@ public: Group *PreviousGroup(); Group *RunningMeshGroup(); bool IsMeshGroup(); + void GenerateShellAndMesh(); template void GenerateForStepAndRepeat(T *steps, T *outs); template void GenerateForBoolean(T *a, T *b, T *o, Group::CombineAs how); void GenerateDisplayItems(); - void DrawDisplayItems(Group::Type t); - void Draw(); - RgbaColor GetLoopSetFillColor(SBezierLoopSet *sbls, - bool *allSame, Vector *errorAt); - void FillLoopSetAsPolygon(SBezierLoopSet *sbls); - void DrawFilledPaths(); + + enum class DrawMeshAs { DEFAULT, HOVERED, SELECTED }; + void DrawMesh(DrawMeshAs how, Canvas *canvas); + void Draw(Canvas *canvas); + void DrawPolyError(Canvas *canvas); + void DrawFilledPaths(Canvas *canvas); SPolygon GetPolygon(); @@ -482,8 +478,7 @@ public: // POD members with indeterminate value. Entity() : EntityBase({}), forceHidden(), actPoint(), actNormal(), actDistance(), actVisible(), style(), construction(), - beziers(), edges(), edgesChordTol(), screenBBox(), screenBBoxValid(), - dogd() {}; + beziers(), edges(), edgesChordTol(), screenBBox(), screenBBoxValid() {}; // A linked entity that was hidden in the source file ends up hidden // here too. @@ -507,42 +502,26 @@ public: BBox screenBBox; bool screenBBoxValid; - // Routines to draw and hit-test the representation of the entity - // on-screen. - struct { - bool drawing; - Point2d mp; - double dmin; - double lineWidth; - double stippleScale; - StipplePattern stippleType; - int data; - } dogd; // state for drawing or getting distance (for hit testing) - - void LineDrawOrGetDistance(Vector a, Vector b, bool maybeFat=false, int userData = -1); - void DrawOrGetDistance(); - bool IsStylable() const; bool IsVisible() const; - bool PointIsFromReferences() const; + + enum class DrawAs { DEFAULT, HIDDEN, HOVERED, SELECTED }; + void Draw(DrawAs how, Canvas *canvas); + void GetReferencePoints(std::vector *refs); + int GetPositionOfPoint(const Camera &camera, Point2d p); void ComputeInterpolatingSpline(SBezierList *sbl, bool periodic) const; void GenerateBezierCurves(SBezierList *sbl) const; void GenerateEdges(SEdgeList *el); - static void DrawAll(bool drawAsHidden); - void Draw(bool drawAsHidden); - double GetDistance(Point2d mp); - Vector GetReferencePos(); + SBezierList *GetOrGenerateBezierCurves(); + SEdgeList *GetOrGenerateEdges(); + BBox GetOrGenerateScreenBBox(bool *hasBBox); void CalculateNumerical(bool forExport); std::string DescriptionString() const; - SBezierList *GetOrGenerateBezierCurves(); - SEdgeList *GetOrGenerateEdges(); - BBox GetOrGenerateScreenBBox(bool *hasBBox); - void Clear() { beziers.l.Clear(); edges.l.Clear(); @@ -668,7 +647,7 @@ public: class Constraint : public ConstraintBase { public: // See Entity::Entity(). - Constraint() : ConstraintBase({}), disp(), dogd() {} + Constraint() : ConstraintBase({}), disp() {} // These define how the constraint is drawn on-screen. struct { @@ -676,39 +655,41 @@ public: hStyle style; } disp; - // State for drawing or getting distance (for hit testing). - struct { - bool drawing; - Point2d mp; - double dmin; - SEdgeList *sel; - } dogd; - - double GetDistance(Point2d mp); - Vector GetLabelPos(); - void GetReferencePos(Vector *refps); - void Draw(); - void GetEdges(SEdgeList *sel); + bool IsVisible() const; bool IsStylable() const; hStyle GetStyle() const; bool HasLabel() const; - - void LineDrawOrGetDistance(Vector a, Vector b); - bool IsVisible() const; - void DrawOrGetDistance(Vector *labelPos, Vector *refps); std::string Label() const; - bool DoLineExtend(Vector p0, Vector p1, Vector pt, double salient); - void DoArcForAngle(Vector a0, Vector da, Vector b0, Vector db, - Vector offset, Vector *ref, bool trim); - void DoArrow(Vector p, Vector dir, Vector n, double width, double angle, double da); - void DoLineWithArrows(Vector ref, Vector a, Vector b, bool onlyOneExt); - int DoLineTrimmedAgainstBox(Vector ref, Vector a, Vector b, bool extend, Vector gr, Vector gu, double swidth, double sheight); - int DoLineTrimmedAgainstBox(Vector ref, Vector a, Vector b, bool extend = true); - void DoLabel(Vector ref, Vector *labelPos, Vector gr, Vector gu); - void StippledLine(Vector a, Vector b); - void DoProjectedPoint(Vector *p); - void DoEqualLenTicks(Vector a, Vector b, Vector gn, Vector *refp); - void DoEqualRadiusTicks(hEntity he, Vector *refp); + + enum class DrawAs { DEFAULT, HOVERED, SELECTED }; + void Draw(DrawAs how, Canvas *canvas); + Vector GetLabelPos(const Camera &camera); + void GetReferencePoints(const Camera &camera, std::vector *refs); + + void DoLayout(DrawAs how, Canvas *canvas, + Vector *labelPos, std::vector *refs); + bool DoLineExtend(Canvas *canvas, Canvas::hStroke hcs, + Vector p0, Vector p1, Vector pt, double salient); + void DoArcForAngle(Canvas *canvas, Canvas::hStroke hcs, + Vector a0, Vector da, Vector b0, Vector db, + Vector offset, Vector *ref, bool trim); + void DoArrow(Canvas *canvas, Canvas::hStroke hcs, + Vector p, Vector dir, Vector n, double width, double angle, double da); + void DoLineWithArrows(Canvas *canvas, Canvas::hStroke hcs, + Vector ref, Vector a, Vector b, bool onlyOneExt); + int DoLineTrimmedAgainstBox(Canvas *canvas, Canvas::hStroke hcs, + Vector ref, Vector a, Vector b, bool extend, + Vector gr, Vector gu, double swidth, double sheight); + int DoLineTrimmedAgainstBox(Canvas *canvas, Canvas::hStroke hcs, + Vector ref, Vector a, Vector b, bool extend = true); + void DoLabel(Canvas *canvas, Canvas::hStroke hcs, + Vector ref, Vector *labelPos, Vector gr, Vector gu); + void DoProjectedPoint(Canvas *canvas, Canvas::hStroke hcs, + Vector *p); + void DoEqualLenTicks(Canvas *canvas, Canvas::hStroke hcs, + Vector a, Vector b, Vector gn, Vector *refp); + void DoEqualRadiusTicks(Canvas *canvas, Canvas::hStroke hcs, + hEntity he, Vector *refp); std::string DescriptionString() const; @@ -831,9 +812,9 @@ public: static Style *Get(hStyle hs); static RgbaColor Color(hStyle hs, bool forExport=false); static RgbaColor FillColor(hStyle hs, bool forExport=false); - static float Width(hStyle hs); + static double Width(hStyle hs); static RgbaColor Color(int hs, bool forExport=false); - static float Width(int hs); + static double Width(int hs); static double WidthMm(int hs); static double TextHeight(hStyle hs); static double DefaultTextHeight(); diff --git a/src/solvespace.h b/src/solvespace.h index 6e175511..010b9610 100644 --- a/src/solvespace.h +++ b/src/solvespace.h @@ -17,25 +17,16 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include -#ifdef WIN32 -# include // required by GL headers -#endif -#ifdef __APPLE__ -# include // for strcasecmp in file.cpp -# include -# include -#else -# include -# include -#endif // We declare these in advance instead of simply using FT_Library // (defined as typedef FT_LibraryRec_* FT_Library) because including @@ -153,6 +144,9 @@ enum class ContextCommand : uint32_t; #define PATH_SEP "/" #endif +extern const bool FLIP_FRAMEBUFFER; + +bool PathEqual(const std::string &a, const std::string &b); FILE *ssfopen(const std::string &filename, const char *mode); void ssremove(const std::string &filename); @@ -304,6 +298,7 @@ class SSurface; #include "dsc.h" #include "polygon.h" #include "srf/surface.h" +#include "render/render.h" class Entity; class hEntity; @@ -346,56 +341,6 @@ public: utf8_iterator end() const { return utf8_iterator(&str[str.length()]); } }; -void ssglLineWidth(GLfloat width); -void ssglVertex3v(Vector u); -void ssglAxisAlignedQuad(double l, double r, double t, double b, bool lone = true); -void ssglAxisAlignedLineLoop(double l, double r, double t, double b); -#ifdef WIN32 -# define SSGL_CALLBACK __stdcall -#else -# define SSGL_CALLBACK -#endif -extern "C" { typedef void SSGL_CALLBACK ssglCallbackFptr(); } -void ssglTesselatePolygon(GLUtesselator *gt, SPolygon *p); -void ssglFillPolygon(SPolygon *p); -void ssglFillMesh(bool useSpecColor, RgbaColor color, - SMesh *m, uint32_t h, uint32_t s1, uint32_t s2); -void ssglDebugPolygon(SPolygon *p); -void ssglDrawEdges(SEdgeList *l, bool endpointsToo, hStyle hs); -void ssglDrawOutlines(SOutlineList *l, Vector projDir, hStyle hs); -void ssglDebugMesh(SMesh *m); -void ssglMarkPolygonNormal(SPolygon *p); -typedef void ssglLineFn(void *data, Vector a, Vector b); -void ssglWriteText(const std::string &str, double h, Vector t, Vector u, Vector v, - ssglLineFn *fn, void *fndata); -void ssglWriteTextRefCenter(const std::string &str, double h, Vector t, Vector u, Vector v, - ssglLineFn *fn, void *fndata); -double ssglStrCapHeight(double h); -double ssglStrFontSize(double h); -double ssglStrWidth(const std::string &str, double h); -void ssglLockColorTo(RgbaColor rgb); -void ssglStippledLine(Vector a, Vector b, double width, - StipplePattern stippleType, double stippleScale, bool maybeFat); -void ssglStippledLine(Vector a, Vector b, double width, - const char *stipplePattern, double stippleScale, bool maybeFat); -void ssglFatLine(Vector a, Vector b, double width); -void ssglUnlockColor(); -void ssglColorRGB(RgbaColor rgb); -void ssglColorRGBa(RgbaColor rgb, double a); -void ssglDepthRangeOffset(int units); -void ssglDepthRangeLockToFront(bool yes); -void ssglDrawPixmap(const Pixmap &pixmap, Vector a, Vector b, Vector c, Vector d); -void ssglDrawPixmap(const Pixmap &pixmap, Point2d o, bool flip = false); -void ssglInitializeBitmapFont(); -void ssglBitmapText(const std::string &str, Vector p); -double ssglBitmapCharQuad(char32_t chr, double x, double y); -int ssglBitmapCharWidth(char32_t chr); -#define TEXTURE_BACKGROUND_IMG 10 -#define TEXTURE_DRAW_PIXELS 20 -#define TEXTURE_COLOR_PICKER_2D 30 -#define TEXTURE_COLOR_PICKER_1D 40 -#define TEXTURE_BITMAP_FONT 50 - #define arraylen(x) (sizeof((x))/sizeof((x)[0])) #define PI (3.1415926535897931) @@ -878,7 +823,7 @@ public: Vector ptB; } extraLine; struct { - Pixmap pixmap; + std::shared_ptr pixmap; double scale; // pixels per mm Vector origin; } bgImage; diff --git a/src/srf/surface.h b/src/srf/surface.h index ebc716d5..5d5f1ae4 100644 --- a/src/srf/surface.h +++ b/src/srf/surface.h @@ -10,10 +10,6 @@ #ifndef __SURFACE_H #define __SURFACE_H -#ifdef WIN32 - #undef DIFFERENCE -#endif - // Utility functions, Bernstein polynomials of order 1-3 and their derivatives. double Bernstein(int k, int deg, double t); double BernsteinDerivative(int k, int deg, double t); diff --git a/src/style.cpp b/src/style.cpp index ef818733..9d8ffaed 100644 --- a/src/style.cpp +++ b/src/style.cpp @@ -6,9 +6,6 @@ // Copyright 2008-2013 Jonathan Westhues. //----------------------------------------------------------------------------- #include "solvespace.h" -#include - -#define DEFAULT_TEXT_HEIGHT 11.5 const Style::Default Style::Defaults[] = { { { ACTIVE_GRP }, "ActiveGrp", RGBf(1.0, 1.0, 1.0), 1.5, 4 }, @@ -88,8 +85,8 @@ void Style::FillDefaultStyle(Style *s, const Default *d, bool factory) { s->color = (factory) ? d->color : CnfThawColor(d->color, CnfColor(d->cnfPrefix)); s->width = (factory) ? d->width : CnfThawFloat((float)(d->width), CnfWidth(d->cnfPrefix)); s->widthAs = UnitsAs::PIXELS; - s->textHeight = (factory) ? DEFAULT_TEXT_HEIGHT - : CnfThawFloat(DEFAULT_TEXT_HEIGHT, CnfTextHeight(d->cnfPrefix)); + s->textHeight = (factory) ? 11.5 + : CnfThawFloat(11.5, CnfTextHeight(d->cnfPrefix)); s->textHeightAs = UnitsAs::PIXELS; s->textOrigin = TextOrigin::NONE; s->textAngle = 0; @@ -110,7 +107,7 @@ void Style::LoadFactoryDefaults() { FillDefaultStyle(s, d, /*factory=*/true); } SS.backgroundColor = RGBi(0, 0, 0); - SS.bgImage.pixmap.Clear(); + SS.bgImage.pixmap = nullptr; } void Style::FreezeDefaultStyles() { @@ -200,7 +197,7 @@ RgbaColor Style::Color(int s, bool forExport) { hStyle hs = { (uint32_t)s }; return Color(hs, forExport); } -float Style::Width(int s) { +double Style::Width(int s) { hStyle hs = { (uint32_t)s }; return Width(hs); } @@ -250,16 +247,13 @@ RgbaColor Style::FillColor(hStyle h, bool forExport) { //----------------------------------------------------------------------------- // Return the width associated with our style in pixels.. //----------------------------------------------------------------------------- -float Style::Width(hStyle h) { - double r = 1.0; +double Style::Width(hStyle h) { Style *s = Get(h); - if(s->widthAs == UnitsAs::MM) { - r = s->width * SS.GW.scale; - } else if(s->widthAs == UnitsAs::PIXELS) { - r = s->width; + switch(s->widthAs) { + case UnitsAs::MM: return s->width * SS.GW.scale; + case UnitsAs::PIXELS: return s->width; } - // This returns a float because ssglLineWidth expects a float, avoid casts. - return (float)r; + ssassert(false, "Unexpected units"); } //----------------------------------------------------------------------------- @@ -273,13 +267,13 @@ double Style::WidthMm(int hs) { //----------------------------------------------------------------------------- // Return the associated text height, in pixels. //----------------------------------------------------------------------------- -double Style::TextHeight(hStyle hs) { - Style *s = Get(hs); - if(s->textHeightAs == UnitsAs::MM) { - return s->textHeight * SS.GW.scale; - } else /* s->textHeightAs == UNITS_AS_PIXELS */ { - return s->textHeight; +double Style::TextHeight(hStyle h) { + Style *s = Get(h); + switch(s->textHeightAs) { + case UnitsAs::MM: return s->textHeight * SS.GW.scale; + case UnitsAs::PIXELS: return s->textHeight; } + ssassert(false, "Unexpected units"); } double Style::DefaultTextHeight() { @@ -369,29 +363,20 @@ void TextWindow::ScreenChangeBackgroundColor(int link, uint32_t v) { SS.TW.edit.meaning = Edit::BACKGROUND_COLOR; } -static int RoundUpToPowerOfTwo(int v) -{ - int i; - for(i = 0; i < 31; i++) { - int vt = (1 << i); - if(vt >= v) { - return vt; - } - } - return 0; -} - void TextWindow::ScreenBackgroundImage(int link, uint32_t v) { - SS.bgImage.pixmap.Clear(); + SS.bgImage.pixmap = nullptr; if(link == 'l') { std::string bgImageFile; if(GetOpenFile(&bgImageFile, "", PngFileFilter)) { FILE *f = ssfopen(bgImageFile, "rb"); if(f) { - SS.bgImage.pixmap = Pixmap::FromPNG(f); + SS.bgImage.pixmap = Pixmap::ReadPng(f); SS.bgImage.scale = SS.GW.scale; SS.bgImage.origin = SS.GW.offset.ScaledBy(-1); + fclose(f); + } else { + Error("Error reading PNG file '%s'", bgImageFile.c_str()); } } } @@ -432,9 +417,9 @@ void TextWindow::ShowListOfStyles() { Printf(false, ""); Printf(false, "%Ft background bitmap image%E"); - if(!SS.bgImage.pixmap.IsEmpty()) { + if(SS.bgImage.pixmap) { Printf(false, "%Ba %Ftwidth:%E %dpx %Ftheight:%E %dpx", - SS.bgImage.pixmap.width, SS.bgImage.pixmap.height); + SS.bgImage.pixmap->width, SS.bgImage.pixmap->height); Printf(false, " %Ftscale:%E %# px/%s %Fl%Ll%f%D[change]%E", SS.bgImage.scale*SS.MmPerUnit(), diff --git a/src/textwin.cpp b/src/textwin.cpp index 2e9c8eb8..fb89cdef 100644 --- a/src/textwin.cpp +++ b/src/textwin.cpp @@ -254,7 +254,7 @@ void TextWindow::Printf(bool halfLine, const char *fmt, ...) { } for(utf8_iterator it(buf); *it; ++it) { - for(int i = 0; i < ssglBitmapCharWidth(*it); i++) { + for(size_t i = 0; i < BitmapFont::Builtin()->GetWidth(*it); i++) { if(c >= MAX_COLS) goto done; text[r][c] = (i == 0) ? *it : ' '; meta[r][c].fg = fg; @@ -344,7 +344,8 @@ void TextWindow::TimerCallback() InvalidateText(); } -void TextWindow::DrawOrHitTestIcons(TextWindow::DrawOrHitHow how, double mx, double my) +void TextWindow::DrawOrHitTestIcons(UiCanvas *uiCanvas, TextWindow::DrawOrHitHow how, + double mx, double my) { int width, height; GetTextWindowSize(&width, &height); @@ -353,10 +354,9 @@ void TextWindow::DrawOrHitTestIcons(TextWindow::DrawOrHitHow how, double mx, dou y -= scrollPos*(LINE_HEIGHT/2); if(how == PAINT) { - double grey = 30.0/255; - double top = y - 28, bot = y + 4; - glColor4d(grey, grey, grey, 1.0); - ssglAxisAlignedQuad(0, width, top, bot); + int top = y - 28, bot = y + 4; + uiCanvas->DrawRect(0, width, top, bot, + /*fillColor=*/{ 30, 30, 30, 255 }, /*outlineColor=*/{}); } HideShowIcon *oldHovered = hoveredIcon; @@ -369,38 +369,29 @@ void TextWindow::DrawOrHitTestIcons(TextWindow::DrawOrHitHow how, double mx, dou if(hsi->var == &SPACER) { // Draw a darker-grey spacer in between the groups of icons. if(how == PAINT) { - int l = x, r = l + 4, - t = y, b = t - 24; - glColor4d(0.17, 0.17, 0.17, 1); - ssglAxisAlignedQuad(l, r, t, b); + uiCanvas->DrawRect(x, x + 4, y, y - 24, + /*fillColor=*/{ 45, 45, 45, 255 }, /*outlineColor=*/{}); } x += 12; continue; } - if(hsi->icon.IsEmpty()) { - hsi->icon = LoadPNG(ssprintf("icons/text-window/%s.png", hsi->iconName)); + if(hsi->icon == nullptr) { + hsi->icon = LoadPng(ssprintf("icons/text-window/%s.png", hsi->iconName)); } if(how == PAINT) { - glColor4d(0, 0, 0, 1.0); - Point2d o = { (double)x, (double)(y - 24) }; - ssglDrawPixmap(hsi->icon, o); + uiCanvas->DrawPixmap(hsi->icon, x, y - 24); if(hsi == hoveredIcon) { - glColor4d(1, 1, 0, 0.3); - ssglAxisAlignedQuad(x - 2, x + 26, y + 2, y - 26); + uiCanvas->DrawRect(x - 2, x + 26, y + 2, y - 26, + /*fillColor=*/{ 255, 255, 0, 75 }, /*outlineColor=*/{}); } if(!*(hsi->var)) { - glColor4d(1, 0, 0, 0.6); - glLineWidth(2); + RgbaColor color = { 255, 0, 0, 150 }; int s = 0, f = 24; - glBegin(GL_LINES); - glVertex2d(x+s, y-s); - glVertex2d(x+f, y-f); - glVertex2d(x+s, y-f); - glVertex2d(x+f, y-s); - glEnd(); + uiCanvas->DrawLine(x+s, y-s, x+f, y-f, color, 2); + uiCanvas->DrawLine(x+s, y-f, x+f, y-s, color, 2); } } else { if(mx > x - 2 && mx < x + 26 && @@ -443,22 +434,17 @@ void TextWindow::DrawOrHitTestIcons(TextWindow::DrawOrHitHow how, double mx, dou tooltippedIcon->tip); } - double ox = oldMousePos.x, oy = oldMousePos.y - LINE_HEIGHT; + int ox = (int)oldMousePos.x, oy = (int)oldMousePos.y - LINE_HEIGHT; ox += 3; oy -= 3; int tw = (str.length() + 1) * (CHAR_WIDTH - 1); - ox = min(ox, (double) (width - 25) - tw); - oy = max(oy, 5.0); + ox = min(ox, (width - 25) - tw); + oy = max(oy, 5); - ssglInitializeBitmapFont(); - glLineWidth(1); - glColor4d(1.0, 1.0, 0.6, 1.0); - ssglAxisAlignedQuad(ox, ox+tw, oy, oy+LINE_HEIGHT); - glColor4d(0.0, 0.0, 0.0, 1.0); - ssglAxisAlignedLineLoop(ox, ox+tw, oy, oy+LINE_HEIGHT); - - glColor4d(0, 0, 0, 1); - ssglBitmapText(str, Vector::From(ox+5, oy-3+LINE_HEIGHT, 0)); + uiCanvas->DrawRect(ox, ox+tw, oy, oy+LINE_HEIGHT, + /*fillColor=*/{ 255, 255, 150, 255 }, + /*outlineColor=*/{ 0, 0, 0, 255 }); + uiCanvas->DrawBitmapText(str, ox+5, oy-3+LINE_HEIGHT, { 0, 0, 0, 255 }); } else { if(!hoveredIcon || (hoveredIcon != tooltippedIcon)) @@ -504,44 +490,36 @@ Vector TextWindow::HsvToRgb(Vector hsv) { return rgb; } -uint8_t *TextWindow::HsvPattern2d() { - static uint8_t Texture[256*256*3]; - static bool Init; - - if(!Init) { - int i, j, p; - p = 0; - for(i = 0; i < 256; i++) { - for(j = 0; j < 256; j++) { - Vector hsv = Vector::From(6.0*i/255.0, 1.0*j/255.0, 1); - Vector rgb = HsvToRgb(hsv); - rgb = rgb.ScaledBy(255); - Texture[p++] = (uint8_t)rgb.x; - Texture[p++] = (uint8_t)rgb.y; - Texture[p++] = (uint8_t)rgb.z; - } +std::shared_ptr TextWindow::HsvPattern2d(int w, int h) { + std::shared_ptr pixmap = Pixmap::Create(Pixmap::Format::RGB, w, h); + for(size_t j = 0; j < pixmap->height; j++) { + size_t p = pixmap->stride * j; + for(size_t i = 0; i < pixmap->width; i++) { + Vector hsv = Vector::From(6.0*i/(pixmap->width-1), 1.0*j/(pixmap->height-1), 1); + Vector rgb = HsvToRgb(hsv); + rgb = rgb.ScaledBy(255); + pixmap->data[p++] = (uint8_t)rgb.x; + pixmap->data[p++] = (uint8_t)rgb.y; + pixmap->data[p++] = (uint8_t)rgb.z; } - Init = true; } - return Texture; + return pixmap; } -uint8_t *TextWindow::HsvPattern1d(double h, double s) { - static uint8_t Texture[256*4]; - - int i, p; - p = 0; - for(i = 0; i < 256; i++) { - Vector hsv = Vector::From(6*h, s, 1.0*(255 - i)/255.0); - Vector rgb = HsvToRgb(hsv); - rgb = rgb.ScaledBy(255); - Texture[p++] = (uint8_t)rgb.x; - Texture[p++] = (uint8_t)rgb.y; - Texture[p++] = (uint8_t)rgb.z; - // Needs a padding byte, to make things four-aligned - p++; +std::shared_ptr TextWindow::HsvPattern1d(double hue, double sat, int w, int h) { + std::shared_ptr pixmap = Pixmap::Create(Pixmap::Format::RGB, w, h); + for(size_t i = 0; i < pixmap->height; i++) { + size_t p = i * pixmap->stride; + for(size_t j = 0; j < pixmap->width; j++) { + Vector hsv = Vector::From(6*hue, sat, 1.0*(pixmap->width - 1 - j)/pixmap->width); + Vector rgb = HsvToRgb(hsv); + rgb = rgb.ScaledBy(255); + pixmap->data[p++] = (uint8_t)rgb.x; + pixmap->data[p++] = (uint8_t)rgb.y; + pixmap->data[p++] = (uint8_t)rgb.z; + } } - return Texture; + return pixmap; } void TextWindow::ColorPickerDone() { @@ -549,7 +527,7 @@ void TextWindow::ColorPickerDone() { EditControlDone(ssprintf("%.2f, %.2f, %.3f", rgb.redF(), rgb.greenF(), rgb.blueF()).c_str()); } -bool TextWindow::DrawOrHitTestColorPicker(DrawOrHitHow how, bool leftDown, +bool TextWindow::DrawOrHitTestColorPicker(UiCanvas *uiCanvas, DrawOrHitHow how, bool leftDown, double x, double y) { bool mousePointerAsHand = false; @@ -597,10 +575,12 @@ bool TextWindow::DrawOrHitTestColorPicker(DrawOrHitHow how, bool leftDown, int bw = 6; if(how == PAINT) { - glColor4d(0.2, 0.2, 0.2, 1); - ssglAxisAlignedQuad(px, pxm+bw, py, pym+bw); - glColor4d(0.0, 0.0, 0.0, 1); - ssglAxisAlignedQuad(px+(bw/2), pxm+(bw/2), py+(bw/2), pym+(bw/2)); + uiCanvas->DrawRect(px, pxm+bw, py, pym+bw, + /*fillColor=*/{ 50, 50, 50, 255 }, + /*outlineColor=*/{}); + uiCanvas->DrawRect(px+(bw/2), pxm+(bw/2), py+(bw/2), pym+(bw/2), + /*fillColor=*/{ 0, 0, 0, 255 }, + /*outlineColor=*/{}); } else { if(x < px || x > pxm+(bw/2) || y < py || y > pym+(bw/2)) @@ -639,8 +619,9 @@ bool TextWindow::DrawOrHitTestColorPicker(DrawOrHitHow how, bool leftDown, int sx = px + 5 + PITCH*(i + 8) + 4, sy = py + 5 + PITCH*j; if(how == PAINT) { - glColor4d(CO(rgb), 1); - ssglAxisAlignedQuad(sx, sx+SIZE, sy, sy+SIZE); + uiCanvas->DrawRect(sx, sx+SIZE, sy, sy+SIZE, + /*fillColor=*/RGBf(rgb.x, rgb.y, rgb.z), + /*outlineColor=*/{}); } else if(how == CLICK) { if(x >= sx && x <= sx+SIZE && y >= sy && y <= sy+SIZE) { editControl.colorPicker.rgb = RGBf(rgb.x, rgb.y, rgb.z); @@ -659,8 +640,9 @@ bool TextWindow::DrawOrHitTestColorPicker(DrawOrHitHow how, bool leftDown, hxm = hx + PITCH*7 + SIZE; hym = hy + PITCH*2 + SIZE; if(how == PAINT) { - ssglColorRGB(editControl.colorPicker.rgb); - ssglAxisAlignedQuad(hx, hxm, hy, hym); + uiCanvas->DrawRect(hx, hxm, hy, hym, + /*fillColor=*/editControl.colorPicker.rgb, + /*outlineColor=*/{}); } else if(how == CLICK) { if(x >= hx && x <= hxm && y >= hy && y <= hym) { ColorPickerDone(); @@ -677,41 +659,13 @@ bool TextWindow::DrawOrHitTestColorPicker(DrawOrHitHow how, bool leftDown, hym = hy + PITCH*1 + SIZE; // The one-dimensional thing to pick the color's value if(how == PAINT) { - glBindTexture(GL_TEXTURE_2D, TEXTURE_COLOR_PICKER_1D); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - 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); + uiCanvas->DrawPixmap(HsvPattern1d(editControl.colorPicker.h, + editControl.colorPicker.s, + hxm-hx, hym-hy), + hx, hy); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 256, 0, - GL_RGB, GL_UNSIGNED_BYTE, - HsvPattern1d(editControl.colorPicker.h, - editControl.colorPicker.s)); - - glEnable(GL_TEXTURE_2D); - glBegin(GL_QUADS); - glTexCoord2d(0, 0); - glVertex2d(hx, hy); - - glTexCoord2d(1, 0); - glVertex2d(hx, hym); - - glTexCoord2d(1, 1); - glVertex2d(hxm, hym); - - glTexCoord2d(0, 1); - glVertex2d(hxm, hy); - glEnd(); - glDisable(GL_TEXTURE_2D); - - double cx = hx+(hxm-hx)*(1 - editControl.colorPicker.v); - glColor4d(0, 0, 0, 1); - glLineWidth(1); - glBegin(GL_LINES); - glVertex2d(cx, hy); - glVertex2d(cx, hym); - glEnd(); + int cx = hx+(int)((hxm-hx)*(1.0 - editControl.colorPicker.v)); + uiCanvas->DrawLine(cx, hy, cx, hym, { 0, 0, 0, 255 }); } else if(how == CLICK || (how == HOVER && leftDown && editControl.colorPicker.picker1dActive)) { @@ -734,42 +688,12 @@ bool TextWindow::DrawOrHitTestColorPicker(DrawOrHitHow how, bool leftDown, hym = hy + PITCH*6 + SIZE; // Two-dimensional thing to pick a color by hue and saturation if(how == PAINT) { - glBindTexture(GL_TEXTURE_2D, TEXTURE_COLOR_PICKER_2D); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - 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); + uiCanvas->DrawPixmap(HsvPattern2d(hxm-hx, hym-hy), hx, hy); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 256, 256, 0, - GL_RGB, GL_UNSIGNED_BYTE, HsvPattern2d()); - - glEnable(GL_TEXTURE_2D); - glBegin(GL_QUADS); - glTexCoord2d(0, 0); - glVertex2d(hx, hy); - - glTexCoord2d(1, 0); - glVertex2d(hx, hym); - - glTexCoord2d(1, 1); - glVertex2d(hxm, hym); - - glTexCoord2d(0, 1); - glVertex2d(hxm, hy); - glEnd(); - glDisable(GL_TEXTURE_2D); - - glColor4d(1, 1, 1, 1); - glLineWidth(1); - double cx = hx+(hxm-hx)*editControl.colorPicker.h, - cy = hy+(hym-hy)*editControl.colorPicker.s; - glBegin(GL_LINES); - glVertex2d(cx - 5, cy); - glVertex2d(cx + 4, cy); - glVertex2d(cx, cy - 5); - glVertex2d(cx, cy + 4); - glEnd(); + int cx = hx+(int)((hxm-hx)*editControl.colorPicker.h), + cy = hy+(int)((hym-hy)*editControl.colorPicker.s); + uiCanvas->DrawLine(cx - 5, cy, cx + 4, cy, { 255, 255, 255, 255 }); + uiCanvas->DrawLine(cx, cy - 5, cx, cy + 4, { 255, 255, 255, 255 }); } else if(how == CLICK || (how == HOVER && leftDown && editControl.colorPicker.picker2dActive)) { @@ -797,22 +721,23 @@ void TextWindow::Paint() { int width, height; GetTextWindowSize(&width, &height); - // We would like things pixel-exact, to avoid shimmering. - glViewport(0, 0, width, height); - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - glClearColor(0, 0, 0, 1); - glClear(GL_COLOR_BUFFER_BIT); - glColor3d(1, 1, 1); + Camera camera = {}; + camera.width = width; + camera.height = height; + camera.LoadIdentity(); + camera.offset.x = -(double)camera.width / 2.0; + camera.offset.y = -(double)camera.height / 2.0; - glTranslated(-1, 1, 0); - glScaled(2.0/width, -2.0/height, 1); - // Make things round consistently, avoiding exact integer boundary - glTranslated(-0.1, -0.1, 0); + OpenGl1Renderer canvas = {}; + canvas.camera = camera; + canvas.BeginFrame(); + canvas.UpdateProjection(); - halfRows = height / (LINE_HEIGHT/2); + UiCanvas uiCanvas = {}; + uiCanvas.canvas = &canvas; + uiCanvas.flip = true; + + halfRows = camera.height / (LINE_HEIGHT/2); int bottom = top[rows-1] + 2; scrollPos = min(scrollPos, bottom - halfRows); @@ -821,21 +746,9 @@ void TextWindow::Paint() { // Let's set up the scroll bar first MoveTextScrollbarTo(scrollPos, top[rows - 1] + 1, halfRows); - // Create the bitmap font that we're going to use. - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glEnable(GL_BLEND); - // Now paint the window. int r, c, a; for(a = 0; a < 2; a++) { - if(a == 0) { - glBegin(GL_QUADS); - } else if(a == 1) { - glEnable(GL_TEXTURE_2D); - ssglInitializeBitmapFont(); - glBegin(GL_QUADS); - } - for(r = 0; r < rows; r++) { int ltop = top[r]; if(ltop < (scrollPos-1)) continue; @@ -847,31 +760,33 @@ void TextWindow::Paint() { int fg = meta[r][c].fg; int bg = meta[r][c].bg; - RgbaColor bgRgb = meta[r][c].bgRgb; // On the first pass, all the background quads; on the next // pass, all the foreground (i.e., font) quads. if(a == 0) { - int bh = LINE_HEIGHT, adj = -2; + RgbaColor bgRgb = meta[r][c].bgRgb; + int bh = LINE_HEIGHT, adj = 0; if(bg == 'z') { - glColor3f(bgRgb.redF(), bgRgb.greenF(), bgRgb.blueF()); bh = CHAR_HEIGHT; adj += 2; } else { - glColor3fv(&(bgColorTable[bg*3])); + bgRgb = RgbaColor::FromFloat(bgColorTable[bg*3+0], + bgColorTable[bg*3+1], + bgColorTable[bg*3+2]); } if(bg != 'd') { // Move the quad down a bit, so that the descenders // still have the correct background. - y += adj; - ssglAxisAlignedQuad(x, x + CHAR_WIDTH, y, y + bh, /*lone=*/false); - y -= adj; + uiCanvas.DrawRect(x, x + CHAR_WIDTH, y + adj, y + adj + bh, + /*fillColor=*/bgRgb, /*outlineColor=*/{}); } } else if(a == 1) { - glColor3fv(&(fgColorTable[fg*3])); + RgbaColor fgRgb = RgbaColor::FromFloat(fgColorTable[fg*3+0], + fgColorTable[fg*3+1], + fgColorTable[fg*3+2]); if(text[r][c] != ' ') { - ssglBitmapCharQuad(text[r][c], x, y + CHAR_HEIGHT); + uiCanvas.DrawBitmapChar(text[r][c], x, y + CHAR_HEIGHT, fgRgb); } // If this is a link and it's hovered, then draw the @@ -903,29 +818,20 @@ void TextWindow::Paint() { cs++; } - glEnd(); - // Always use the color of the rightmost character // in the link, so that underline is consistent color fg = meta[r][cf-1].fg; - glColor3fv(&(fgColorTable[fg*3])); - glDisable(GL_TEXTURE_2D); - glLineWidth(1); - glBegin(GL_LINES); - int yp = y + CHAR_HEIGHT; - glVertex2d(LEFT_MARGIN + cs*CHAR_WIDTH, yp); - glVertex2d(LEFT_MARGIN + cf*CHAR_WIDTH, yp); - glEnd(); - - glEnable(GL_TEXTURE_2D); - glBegin(GL_QUADS); + fgRgb = RgbaColor::FromFloat(fgColorTable[fg*3+0], + fgColorTable[fg*3+1], + fgColorTable[fg*3+2]); + int yp = y + CHAR_HEIGHT; + uiCanvas.DrawLine(LEFT_MARGIN + cs*CHAR_WIDTH, yp, + LEFT_MARGIN + cf*CHAR_WIDTH, yp, + fgRgb); } } } } - - glEnd(); - glDisable(GL_TEXTURE_2D); } // The line to indicate the column of radio buttons that indicates the @@ -938,24 +844,24 @@ void TextWindow::Paint() { int x = 29, y = 70 + LINE_HEIGHT; y -= scrollPos*(LINE_HEIGHT/2); - glLineWidth(1); - glColor3fv(&(fgColorTable['t'*3])); - glBegin(GL_LINES); - glVertex2d(x, y); - glVertex2d(x, y+40); - glEnd(); + RgbaColor color = RgbaColor::FromFloat(fgColorTable['t'*3+0], + fgColorTable['t'*3+1], + fgColorTable['t'*3+2]); + uiCanvas.DrawLine(x, y, x, y+40, color); } // The header has some icons that are drawn separately from the text - DrawOrHitTestIcons(PAINT, 0, 0); + DrawOrHitTestIcons(&uiCanvas, PAINT, 0, 0); // And we may show a color picker for certain editable fields - DrawOrHitTestColorPicker(PAINT, false, 0, 0); + DrawOrHitTestColorPicker(&uiCanvas, PAINT, false, 0, 0); + + canvas.EndFrame(); } void TextWindow::MouseEvent(bool leftClick, bool leftDown, double x, double y) { if(TextEditControlIsVisible() || GraphicsEditControlIsVisible()) { - if(DrawOrHitTestColorPicker(leftClick ? CLICK : HOVER, leftDown, x, y)) + if(DrawOrHitTestColorPicker(NULL, leftClick ? CLICK : HOVER, leftDown, x, y)) { return; } @@ -969,7 +875,7 @@ void TextWindow::MouseEvent(bool leftClick, bool leftDown, double x, double y) { return; } - DrawOrHitTestIcons(leftClick ? CLICK : HOVER, x, y); + DrawOrHitTestIcons(NULL, leftClick ? CLICK : HOVER, x, y); GraphicsWindow::Selection ps = SS.GW.hover; SS.GW.hover.Clear(); diff --git a/src/toolbar.cpp b/src/toolbar.cpp index 8bc9c86e..68e81fee 100644 --- a/src/toolbar.cpp +++ b/src/toolbar.cpp @@ -11,7 +11,7 @@ struct ToolIcon { std::string name; Command command; std::string tooltip; - Pixmap pixmap; + std::shared_ptr pixmap; }; static ToolIcon Toolbar[] = { { "line", Command::LINE_SEGMENT, "Sketch line segment", {} }, @@ -53,8 +53,8 @@ static ToolIcon Toolbar[] = { { "ontoworkplane", Command::ONTO_WORKPLANE, "Align view to active workplane", {} }, }; -void GraphicsWindow::ToolbarDraw() { - ToolbarDrawOrHitTest(0, 0, /*paint=*/true, NULL); +void GraphicsWindow::ToolbarDraw(UiCanvas *canvas) { + ToolbarDrawOrHitTest(0, 0, canvas, NULL); } bool GraphicsWindow::ToolbarMouseMoved(int x, int y) { @@ -62,7 +62,7 @@ bool GraphicsWindow::ToolbarMouseMoved(int x, int y) { y += ((int)height/2); Command nh = Command::NONE; - bool withinToolbar = ToolbarDrawOrHitTest(x, y, /*paint=*/false, &nh); + bool withinToolbar = ToolbarDrawOrHitTest(x, y, NULL, &nh); if(!withinToolbar) nh = Command::NONE; if(nh != toolbarTooltipped) { @@ -89,7 +89,7 @@ bool GraphicsWindow::ToolbarMouseDown(int x, int y) { y += ((int)height/2); Command nh = Command::NONE; - bool withinToolbar = ToolbarDrawOrHitTest(x, y, /*paint=*/false, &nh); + bool withinToolbar = ToolbarDrawOrHitTest(x, y, NULL, &nh); // They might have clicked within the toolbar, but not on a button. if(withinToolbar && nh != Command::NONE) { for(int i = 0; SS.GW.menu[i].level >= 0; i++) { @@ -103,7 +103,7 @@ bool GraphicsWindow::ToolbarMouseDown(int x, int y) { } bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my, - bool paint, Command *menuHit) + UiCanvas *canvas, Command *menuHit) { int i; int x = 17, y = (int)(height - 52); @@ -115,23 +115,15 @@ bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my, bool withinToolbar = (mx >= aleft && mx <= aright && my <= atop && my >= abot); - if(!paint && !withinToolbar) { + if(!canvas && !withinToolbar) { // This gets called every MouseMove event, so return quickly. return false; } - if(paint) { - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glTranslated(-1, -1, 0); - glScaled(2.0/width, 2.0/height, 0); - glDisable(GL_LIGHTING); - - double c = 30.0/255; - glColor4d(c, c, c, 1.0); - ssglAxisAlignedQuad(aleft, aright, atop, abot); + if(canvas) { + canvas->DrawRect(aleft, aright, atop, abot, + /*fillColor=*/{ 30, 30, 30, 255 }, + /*outlineColor=*/{}); } struct { @@ -149,37 +141,34 @@ bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my, } y -= 16; - if(paint) { + if(canvas) { // Draw a separator bar in a slightly different color. int divw = 30, divh = 2; - glColor4d(0.17, 0.17, 0.17, 1); - x += 16; - y += 24; - ssglAxisAlignedQuad(x+divw, x-divw, y+divh, y-divh); - x -= 16; - y -= 24; + canvas->DrawRect(x+16+divw, x+16-divw, y+24+divh, y+24-divh, + /*fillColor=*/{ 45, 45, 45, 255 }, + /*outlineColor=*/{}); } continue; } - if(icon.pixmap.IsEmpty()) { - icon.pixmap = LoadPNG("icons/graphics-window/" + icon.name + ".png"); + if(icon.pixmap == nullptr) { + icon.pixmap = LoadPng("icons/graphics-window/" + icon.name + ".png"); } - if(paint) { - glColor4d(0, 0, 0, 1.0); - Point2d o = { (double)(x - icon.pixmap.width / 2), - (double)(y - icon.pixmap.height / 2) }; - ssglDrawPixmap(icon.pixmap, o, /*flip=*/true); + if(canvas) { + canvas->DrawPixmap(icon.pixmap, + x - icon.pixmap->width / 2, + y - icon.pixmap->height / 2); if(toolbarHovered == icon.command || (pending.operation == Pending::COMMAND && pending.command == icon.command)) { // Highlight the hovered or pending item. - glColor4d(1, 1, 0, 0.3); int boxhw = 15; - ssglAxisAlignedQuad(x+boxhw, x-boxhw, y+boxhw, y-boxhw); + canvas->DrawRect(x+boxhw, x-boxhw, y+boxhw, y-boxhw, + /*fillColor=*/{ 255, 255, 0, 75 }, + /*outlineColor=*/{}); } if(toolbarTooltipped == icon.command) { @@ -208,10 +197,9 @@ bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my, } } - if(paint) { + if(canvas) { // Do this last so that nothing can draw over it. if(toolTip.show) { - ssglInitializeBitmapFont(); std::string str = toolTip.str; for(i = 0; SS.GW.menu[i].level >= 0; i++) { @@ -224,24 +212,15 @@ bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my, } } - int tw = str.length() * (SS.TW.CHAR_WIDTH - 1) + 10, + int tw = BitmapFont::Builtin()->GetWidth(str) * 8 + 10, th = SS.TW.LINE_HEIGHT + 2; - double ox = toolbarMouseX + 3, oy = toolbarMouseY + 3; - glLineWidth(1); - glColor4d(1.0, 1.0, 0.6, 1.0); - ssglAxisAlignedQuad(ox, ox+tw, oy, oy+th); - glColor4d(0.0, 0.0, 0.0, 1.0); - ssglAxisAlignedLineLoop(ox, ox+tw, oy, oy+th); - - glColor4d(0, 0, 0, 1); - glPushMatrix(); - glTranslated(ox+5, oy+3, 0); - glScaled(1, -1, 1); - ssglBitmapText(str, Vector::From(0, 0, 0)); - glPopMatrix(); + int ox = toolbarMouseX + 3, oy = toolbarMouseY + 3; + canvas->DrawRect(ox, ox+tw, oy, oy+th, + /*fillColor=*/{ 255, 255, 150, 255 }, + /*outlineColor=*/{ 0, 0, 0, 255 }); + canvas->DrawBitmapText(str, ox+5, oy+4, { 0, 0, 0, 255 }); } - ssglDepthRangeLockToFront(false); } return withinToolbar; diff --git a/src/ui.h b/src/ui.h index 8fd79770..7753ae33 100644 --- a/src/ui.h +++ b/src/ui.h @@ -8,11 +8,6 @@ #ifndef __UI_H #define __UI_H -#ifdef WIN32 - // winnt.h - #undef DELETE -#endif - // This table describes the top-level menus in the graphics winodw. enum class Command : uint32_t { NONE = 0, @@ -202,11 +197,13 @@ public: bool *var; const char *iconName; const char *tip; - Pixmap icon; + std::shared_ptr icon; } HideShowIcon; static HideShowIcon hideShowIcons[]; static bool SPACER; + void Draw(Canvas *canvas); + // These are called by the platform-specific code. void Paint(); void MouseEvent(bool isClick, bool leftDown, double x, double y); @@ -219,16 +216,18 @@ public: HOVER = 1, CLICK = 2 }; - void DrawOrHitTestIcons(DrawOrHitHow how, double mx, double my); + void DrawOrHitTestIcons(UiCanvas *canvas, DrawOrHitHow how, + double mx, double my); void TimerCallback(); Point2d oldMousePos; HideShowIcon *hoveredIcon, *tooltippedIcon; Vector HsvToRgb(Vector hsv); - uint8_t *HsvPattern2d(); - uint8_t *HsvPattern1d(double h, double s); + std::shared_ptr HsvPattern2d(int w, int h); + std::shared_ptr HsvPattern1d(double hue, double sat, int w, int h); void ColorPickerDone(); - bool DrawOrHitTestColorPicker(DrawOrHitHow how, bool leftDown, double x, double y); + bool DrawOrHitTestColorPicker(UiCanvas *canvas, DrawOrHitHow how, + bool leftDown, double x, double y); void Init(); void MakeColorTable(const Color *in, float *out); @@ -467,8 +466,6 @@ public: void EditControlDone(const char *s); }; -#define SELECTION_RADIUS 10.0 - class GraphicsWindow { public: void Init(); @@ -546,6 +543,9 @@ public: bool active; } context; + Camera GetCamera() const; + Lighting GetLighting() const; + void NormalizeProjectionVectors(); Point2d ProjectPoint(Vector p); Vector ProjectPoint3(Vector p); @@ -649,7 +649,7 @@ public: hConstraint constraint; bool emphasized; - void Draw(); + void Draw(bool isHovered, Canvas *canvas); void Clear(); bool IsEmpty(); @@ -702,8 +702,8 @@ public: int64_t contextMenuCancelTime; // The toolbar, in toolbar.cpp - bool ToolbarDrawOrHitTest(int x, int y, bool paint, Command *menuHit); - void ToolbarDraw(); + bool ToolbarDrawOrHitTest(int x, int y, UiCanvas *canvas, Command *menuHit); + void ToolbarDraw(UiCanvas *canvas); bool ToolbarMouseMoved(int x, int y); bool ToolbarMouseDown(int x, int y); static void TimerCallback(); @@ -726,6 +726,7 @@ public: void ToggleBool(bool *v); bool showSnapGrid; + void DrawSnapGrid(Canvas *canvas); void AddPointToDraggedList(hEntity hp); void StartDraggingByEntity(hEntity he); @@ -733,6 +734,9 @@ public: void UpdateDraggedNum(Vector *pos, double mx, double my); void UpdateDraggedPoint(hEntity hp, double mx, double my); + void DrawPersistent(Canvas *canvas); + void Draw(Canvas *canvas); + // These are called by the platform-specific code. void Paint(); void MouseMoved(double x, double y, bool leftDown, bool middleDown, diff --git a/src/undoredo.cpp b/src/undoredo.cpp index c1e8a570..3fe9c3bc 100644 --- a/src/undoredo.cpp +++ b/src/undoredo.cpp @@ -83,7 +83,6 @@ void SolveSpaceUI::PushFromCurrentOnto(UndoStack *uk) { for(i = 0; i < SK.constraint.n; i++) { Constraint *src = &(SK.constraint.elem[i]); Constraint dest = *src; - dest.dogd = {}; ut->constraint.Add(&dest); } for(i = 0; i < SK.param.n; i++) { diff --git a/src/util.cpp b/src/util.cpp index 8269cde4..0b358304 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -1019,6 +1019,23 @@ double Point2d::DistanceToLine(const Point2d &p0, const Point2d &dp, bool asSegm return DistanceTo(closest); } +double Point2d::DistanceToLineSigned(const Point2d &p0, const Point2d &dp, bool asSegment) const { + double m = dp.x*dp.x + dp.y*dp.y; + if(m < LENGTH_EPS*LENGTH_EPS) return VERY_POSITIVE; + + Point2d n = dp.Normal().WithMagnitude(1.0); + double dist = n.Dot(*this) - n.Dot(p0); + if(asSegment) { + // Let our line be p = p0 + t*dp, for a scalar t from 0 to 1 + double t = (dp.x*(x - p0.x) + dp.y*(y - p0.y))/m; + double sign = (dist > 0.0) ? 1.0 : -1.0; + if(t < 0.0) return DistanceTo(p0) * sign; + if(t > 1.0) return DistanceTo(p0.Plus(dp)) * sign; + } + + return dist; +} + Point2d Point2d::Normal() const { return { y, -x }; }