Solvespace
954 строки · 33.7 Кб
1//-----------------------------------------------------------------------------
2// The root function to paint our graphics window, after setting up all the
3// views and such appropriately. Also contains all the stuff to manage the
4// selection.
5//
6// Copyright 2008-2013 Jonathan Westhues.
7//-----------------------------------------------------------------------------
8#include "solvespace.h"9
10bool GraphicsWindow::Selection::Equals(Selection *b) {11if(entity != b->entity) return false;12if(constraint != b->constraint) return false;13return true;14}
15
16bool GraphicsWindow::Selection::IsEmpty() {17if(entity.v) return false;18if(constraint.v) return false;19return true;20}
21
22bool GraphicsWindow::Selection::HasEndpoints() {23if(!entity.v) return false;24Entity *e = SK.GetEntity(entity);25return e->HasEndpoints();26}
27
28void GraphicsWindow::Selection::Clear() {29entity.v = constraint.v = 0;30emphasized = false;31}
32
33void GraphicsWindow::Selection::Draw(bool isHovered, Canvas *canvas) {34const Camera &camera = canvas->GetCamera();35
36std::vector<Vector> refs;37if(entity.v) {38Entity *e = SK.GetEntity(entity);39e->Draw(isHovered ? Entity::DrawAs::HOVERED :40Entity::DrawAs::SELECTED,41canvas);42if(emphasized) {43e->GetReferencePoints(&refs);44}45}46if(constraint.v) {47Constraint *c = SK.GetConstraint(constraint);48c->Draw(isHovered ? Constraint::DrawAs::HOVERED :49Constraint::DrawAs::SELECTED,50canvas);51if(emphasized) {52c->GetReferencePoints(camera, &refs);53}54}55if(emphasized && (constraint.v || entity.v)) {56// We want to emphasize this constraint or entity, by drawing a thick57// line from the top left corner of the screen to the reference point(s)58// of that entity or constraint.59Canvas::Stroke strokeEmphasis = {};60strokeEmphasis.layer = Canvas::Layer::FRONT;61strokeEmphasis.color = Style::Color(Style::HOVERED).WithAlpha(50);62strokeEmphasis.width = 40;63strokeEmphasis.unit = Canvas::Unit::PX;64Canvas::hStroke hcsEmphasis = canvas->GetStroke(strokeEmphasis);65
66Point2d topLeftScreen;67topLeftScreen.x = -(double)camera.width / 2;68topLeftScreen.y = (double)camera.height / 2;69Vector topLeft = camera.UnProjectPoint(topLeftScreen);70
71auto it = std::unique(refs.begin(), refs.end(),72[](Vector a, Vector b) { return a.Equals(b); });73refs.erase(it, refs.end());74for(Vector p : refs) {75canvas->DrawLine(topLeft, p, hcsEmphasis);76}77}78}
79
80void GraphicsWindow::ClearSelection() {81selection.Clear();82SS.ScheduleShowTW();83Invalidate();84}
85
86void GraphicsWindow::ClearNonexistentSelectionItems() {87bool change = false;88Selection *s;89selection.ClearTags();90for(s = selection.First(); s; s = selection.NextAfter(s)) {91if(s->constraint.v && !(SK.constraint.FindByIdNoOops(s->constraint))) {92s->tag = 1;93change = true;94}95if(s->entity.v && !(SK.entity.FindByIdNoOops(s->entity))) {96s->tag = 1;97change = true;98}99}100selection.RemoveTagged();101if(change) Invalidate();102}
103
104//-----------------------------------------------------------------------------
105// Is this entity/constraint selected?
106//-----------------------------------------------------------------------------
107bool GraphicsWindow::IsSelected(hEntity he) {108Selection s = {};109s.entity = he;110return IsSelected(&s);111}
112bool GraphicsWindow::IsSelected(Selection *st) {113Selection *s;114for(s = selection.First(); s; s = selection.NextAfter(s)) {115if(s->Equals(st)) {116return true;117}118}119return false;120}
121
122//-----------------------------------------------------------------------------
123// Unselect an item, if it is selected. We can either unselect just that item,
124// or also unselect any coincident points. The latter is useful if the user
125// somehow selects two coincident points (like with select all), because it
126// would otherwise be impossible to de-select the lower of the two.
127//-----------------------------------------------------------------------------
128void GraphicsWindow::MakeUnselected(hEntity he, bool coincidentPointTrick) {129Selection stog = {};130stog.entity = he;131MakeUnselected(&stog, coincidentPointTrick);132}
133void GraphicsWindow::MakeUnselected(Selection *stog, bool coincidentPointTrick){134if(stog->IsEmpty()) return;135
136Selection *s;137
138// If an item was selected, then we just un-select it.139selection.ClearTags();140for(s = selection.First(); s; s = selection.NextAfter(s)) {141if(s->Equals(stog)) {142s->tag = 1;143}144}145// If two points are coincident, then it's impossible to hover one of146// them. But make sure to deselect both, to avoid mysterious seeming147// inability to deselect if the bottom one did somehow get selected.148if(stog->entity.v && coincidentPointTrick) {149Entity *e = SK.GetEntity(stog->entity);150if(e->IsPoint()) {151Vector ep = e->PointGetNum();152for(s = selection.First(); s; s = selection.NextAfter(s)) {153if(!s->entity.v) continue;154if(s->entity == stog->entity)155continue;156Entity *se = SK.GetEntity(s->entity);157if(!se->IsPoint()) continue;158if(ep.Equals(se->PointGetNum())) {159s->tag = 1;160}161}162}163}164selection.RemoveTagged();165}
166
167//-----------------------------------------------------------------------------
168// Select an item, if it isn't selected already.
169//-----------------------------------------------------------------------------
170void GraphicsWindow::MakeSelected(hEntity he) {171Selection stog = {};172stog.entity = he;173MakeSelected(&stog);174}
175
176void GraphicsWindow::MakeSelected(hConstraint hc) {177Selection stog = {};178stog.constraint = hc;179MakeSelected(&stog);180}
181
182void GraphicsWindow::MakeSelected(Selection *stog) {183if(stog->IsEmpty()) return;184if(IsSelected(stog)) return;185
186if(stog->entity.v != 0 && SK.GetEntity(stog->entity)->IsFace()) {187// In the interest of speed for the triangle drawing code,188// only MAX_SELECTABLE_FACES faces may be selected at a time.189unsigned int c = 0;190Selection *s;191selection.ClearTags();192for(s = selection.First(); s; s = selection.NextAfter(s)) {193hEntity he = s->entity;194if(he.v != 0 && SK.GetEntity(he)->IsFace()) {195c++;196// See also GraphicsWindow::GroupSelection "if(e->IsFace())"197// and Group::DrawMesh "case DrawMeshAs::SELECTED:"198if(c >= MAX_SELECTABLE_FACES) s->tag = 1;199}200}201selection.RemoveTagged();202}203
204selection.Add(stog);205}
206
207//-----------------------------------------------------------------------------
208// Select everything that lies within the marquee view-aligned rectangle.
209//-----------------------------------------------------------------------------
210void GraphicsWindow::SelectByMarquee() {211Point2d marqueePoint = ProjectPoint(orig.marqueePoint);212BBox marqueeBBox = BBox::From(Vector::From(marqueePoint.x, marqueePoint.y, VERY_NEGATIVE),213Vector::From(orig.mouse.x, orig.mouse.y, VERY_POSITIVE));214
215for(Entity &e : SK.entity) {216if(e.group != SS.GW.activeGroup) continue;217if(e.IsFace() || e.IsDistance()) continue;218if(!e.IsVisible()) continue;219
220bool entityHasBBox;221BBox entityBBox = e.GetOrGenerateScreenBBox(&entityHasBBox);222if(entityHasBBox && entityBBox.Overlaps(marqueeBBox)) {223if(e.type == Entity::Type::LINE_SEGMENT) {224Vector p0 = SS.GW.ProjectPoint3(e.EndpointStart());225Vector p1 = SS.GW.ProjectPoint3(e.EndpointFinish());226if((!marqueeBBox.Contains({p0.x, p0.y}, 0)) &&227(!marqueeBBox.Contains({p1.x, p1.y}, 0))) {228// The selection marquee does not contain either of the line segment end points.229// This means that either the segment is entirely outside the marquee or that230// it intersects it. Check if it does...231if(!Vector::BoundingBoxIntersectsLine(marqueeBBox.maxp, marqueeBBox.minp, p0,232p1, true)) {233// ... it does not so it is outside.234continue;235}236}237}238MakeSelected(e.h);239}240}241}
242
243//-----------------------------------------------------------------------------
244// Sort the selection according to various criteria: the entities and
245// constraints separately, counts of certain types of entities (circles,
246// lines, etc.), and so on.
247//-----------------------------------------------------------------------------
248void GraphicsWindow::GroupSelection() {249gs = {};250int i;251for(i = 0; i < selection.n; i++) {252Selection *s = &(selection[i]);253if(s->entity.v) {254(gs.n)++;255
256Entity *e = SK.entity.FindById(s->entity);257
258if(e->IsStylable()) gs.stylables++;259
260// A list of points, and a list of all entities that aren't points.261if(e->IsPoint()) {262gs.points++;263gs.point.push_back(s->entity);264} else {265gs.entities++;266gs.entity.push_back(s->entity);267}268
269// And an auxiliary list of normals, including normals from270// workplanes.271if(e->IsNormal()) {272gs.anyNormals++;273gs.anyNormal.push_back(s->entity);274} else if(e->IsWorkplane()) {275gs.anyNormals++;276gs.anyNormal.push_back(e->Normal()->h);277}278
279// And of vectors (i.e., stuff with a direction to constrain)280if(e->HasVector()) {281gs.vectors++;282gs.vector.push_back(s->entity);283}284
285// Faces (which are special, associated/drawn with triangles)286if(e->IsFace()) {287gs.faces++;288gs.face.push_back(s->entity);289}290
291if(e->HasEndpoints()) {292(gs.withEndpoints)++;293}294
295// And some aux counts too296switch(e->type) {297case Entity::Type::WORKPLANE: (gs.workplanes)++; break;298case Entity::Type::LINE_SEGMENT: (gs.lineSegments)++; break;299case Entity::Type::CUBIC: (gs.cubics)++; break;300case Entity::Type::CUBIC_PERIODIC: (gs.periodicCubics)++; break;301
302case Entity::Type::ARC_OF_CIRCLE:303(gs.circlesOrArcs)++;304(gs.arcs)++;305break;306
307case Entity::Type::CIRCLE: (gs.circlesOrArcs)++; break;308
309default: break;310}311}312if(s->constraint.v) {313gs.constraints++;314gs.constraint.push_back(s->constraint);315Constraint *c = SK.GetConstraint(s->constraint);316if(c->IsStylable()) gs.stylables++;317if(c->HasLabel()) gs.constraintLabels++;318}319}320}
321
322Camera GraphicsWindow::GetCamera() const {323Camera camera = {};324if(window) {325window->GetContentSize(&camera.width, &camera.height);326camera.pixelRatio = window->GetDevicePixelRatio();327camera.gridFit = (window->GetDevicePixelRatio() == 1);328} else { // solvespace-cli329camera.width = 297.0; // A4? Whatever...330camera.height = 210.0;331camera.pixelRatio = 1.0;332camera.gridFit = camera.pixelRatio == 1.0;333}334camera.offset = offset;335camera.projUp = projUp;336camera.projRight = projRight;337camera.scale = scale;338camera.tangent = SS.CameraTangent();339return camera;340}
341
342Lighting GraphicsWindow::GetLighting() const {343Lighting lighting = {};344lighting.backgroundColor = SS.backgroundColor;345lighting.ambientIntensity = SS.ambientIntensity;346lighting.lightIntensity[0] = SS.lightIntensity[0];347lighting.lightIntensity[1] = SS.lightIntensity[1];348lighting.lightDirection[0] = SS.lightDir[0];349lighting.lightDirection[1] = SS.lightDir[1];350return lighting;351}
352
353GraphicsWindow::Selection GraphicsWindow::ChooseFromHoverToSelect() {354Selection sel = {};355if(hoverList.IsEmpty())356return sel;357
358Group *activeGroup = SK.GetGroup(SS.GW.activeGroup);359int bestOrder = -1;360int bestZIndex = 0;361double bestDepth = VERY_POSITIVE;362
363for(const Hover &hov : hoverList) {364hGroup hg = {};365if(hov.selection.entity.v != 0) {366hg = SK.GetEntity(hov.selection.entity)->group;367} else if(hov.selection.constraint.v != 0) {368hg = SK.GetConstraint(hov.selection.constraint)->group;369}370
371Group *g = SK.GetGroup(hg);372if(g->order > activeGroup->order) continue;373if(bestOrder != -1 && (bestOrder > g->order || bestZIndex > hov.zIndex)) continue;374// we have hov.zIndex is >= best and hov.group is >= best (but not > active group)375if(hov.depth > bestDepth && bestOrder == g->order && bestZIndex == hov.zIndex) continue;376bestOrder = g->order;377bestZIndex = hov.zIndex;378bestDepth = hov.depth;379sel = hov.selection;380}381return sel;382}
383
384// This uses the same logic as hovering and static entity selection
385// but ignores points known not to be draggable
386GraphicsWindow::Selection GraphicsWindow::ChooseFromHoverToDrag() {387Selection sel = {};388if(hoverList.IsEmpty())389return sel;390
391Group *activeGroup = SK.GetGroup(SS.GW.activeGroup);392int bestOrder = -1;393int bestZIndex = 0;394double bestDepth = VERY_POSITIVE;395
396for(const Hover &hov : hoverList) {397hGroup hg = {};398if(hov.selection.entity.v != 0) {399Entity *e = SK.GetEntity(hov.selection.entity);400if (!e->CanBeDragged()) continue;401hg = e->group;402} else if(hov.selection.constraint.v != 0) {403hg = SK.GetConstraint(hov.selection.constraint)->group;404}405
406Group *g = SK.GetGroup(hg);407if(g->order > activeGroup->order) continue;408if(bestOrder != -1 && (bestOrder > g->order || bestZIndex > hov.zIndex)) continue;409// we have hov.zIndex is >= best and hov.group is >= best (but not > active group)410if(hov.depth > bestDepth && bestOrder == g->order && bestZIndex == hov.zIndex) continue;411bestOrder = g->order;412bestZIndex = hov.zIndex;413sel = hov.selection;414}415return sel;416}
417
418void GraphicsWindow::HitTestMakeSelection(Point2d mp) {419hoverList = {};420Selection sel = {};421
422// Did the view projection change? If so, invalidate bounding boxes.423if(!offset.EqualsExactly(cached.offset) ||424!projRight.EqualsExactly(cached.projRight) ||425!projUp.EqualsExactly(cached.projUp) ||426EXACT(scale != cached.scale)) {427cached.offset = offset;428cached.projRight = projRight;429cached.projUp = projUp;430cached.scale = scale;431for(Entity &e : SK.entity) {432e.screenBBoxValid = false;433}434}435
436ObjectPicker canvas = {};437canvas.camera = GetCamera();438canvas.selRadius = 10.0;439canvas.point = mp;440canvas.maxZIndex = -1;441
442// Always do the entities; we might be dragging something that should443// be auto-constrained, and we need the hover for that.444for(Entity &e : SK.entity) {445if(!e.IsVisible()) continue;446
447// If faces aren't selectable, image entities aren't either.448if(e.type == Entity::Type::IMAGE && !showFaces) continue;449
450// Don't hover whatever's being dragged.451if(IsFromPending(e.h.request())) {452// The one exception is when we're creating a new cubic; we453// want to be able to hover the first point, because that's454// how we turn it into a periodic spline.455if(!e.IsPoint()) continue;456if(!e.h.isFromRequest()) continue;457Request *r = SK.GetRequest(e.h.request());458if(r->type != Request::Type::CUBIC) continue;459if(r->extraPoints < 2) continue;460if(e.h.v != r->h.entity(1).v) continue;461}462
463if(canvas.Pick([&]{ e.Draw(Entity::DrawAs::DEFAULT, &canvas); })) {464Hover hov = {};465hov.distance = canvas.minDistance;466hov.zIndex = canvas.maxZIndex;467hov.depth = canvas.minDepth;468hov.selection.entity = e.h;469hoverList.Add(&hov);470}471}472
473// The constraints and faces happen only when nothing's in progress.474if(pending.operation == Pending::NONE) {475// Constraints476for(Constraint &c : SK.constraint) {477if(canvas.Pick([&]{ c.Draw(Constraint::DrawAs::DEFAULT, &canvas); })) {478Hover hov = {};479hov.distance = canvas.minDistance;480hov.zIndex = canvas.maxZIndex;481hov.selection.constraint = c.h;482hoverList.Add(&hov);483}484}485}486
487std::sort(hoverList.begin(), hoverList.end(),488[](const Hover &a, const Hover &b) {489if(a.zIndex == b.zIndex) return a.distance < b.distance;490return a.zIndex > b.zIndex;491});492sel = ChooseFromHoverToSelect();493
494if(pending.operation == Pending::NONE) {495// Faces, from the triangle mesh; these are lowest priority496if(sel.constraint.v == 0 && sel.entity.v == 0 && showShaded && showFaces) {497Group *g = SK.GetGroup(activeGroup);498SMesh *m = &(g->displayMesh);499
500uint32_t v = m->FirstIntersectionWith(mp);501if(v) {502sel.entity.v = v;503}504}505}506
507canvas.Clear();508
509if(!sel.Equals(&hover)) {510hover = sel;511Invalidate();512}513}
514
515//-----------------------------------------------------------------------------
516// Project a point in model space to screen space, exactly as gl would; return
517// units are pixels.
518//-----------------------------------------------------------------------------
519Point2d GraphicsWindow::ProjectPoint(Vector p) {520Vector p3 = ProjectPoint3(p);521Point2d p2 = { p3.x, p3.y };522return p2;523}
524//-----------------------------------------------------------------------------
525// Project a point in model space to screen space, exactly as gl would; return
526// units are pixels. The z coordinate is also returned, also in pixels.
527//-----------------------------------------------------------------------------
528Vector GraphicsWindow::ProjectPoint3(Vector p) {529double w;530Vector r = ProjectPoint4(p, &w);531return r.ScaledBy(scale/w);532}
533//-----------------------------------------------------------------------------
534// Project a point in model space halfway into screen space. The scale is
535// not applied, and the perspective divide isn't applied; instead the w
536// coordinate is returned separately.
537//-----------------------------------------------------------------------------
538Vector GraphicsWindow::ProjectPoint4(Vector p, double *w) {539p = p.Plus(offset);540
541Vector r;542r.x = p.Dot(projRight);543r.y = p.Dot(projUp);544r.z = p.Dot(projUp.Cross(projRight));545
546*w = 1 + r.z*SS.CameraTangent()*scale;547return r;548}
549
550//-----------------------------------------------------------------------------
551// Return a point in the plane parallel to the screen and through the offset,
552// that projects onto the specified (x, y) coordinates.
553//-----------------------------------------------------------------------------
554Vector GraphicsWindow::UnProjectPoint(Point2d p) {555Vector orig = offset.ScaledBy(-1);556
557// Note that we ignoring the effects of perspective. Since our returned558// point has the same component normal to the screen as the offset, it559// will have z = 0 after the rotation is applied, thus w = 1. So this is560// correct.561orig = orig.Plus(projRight.ScaledBy(p.x / scale)).Plus(562projUp. ScaledBy(p.y / scale));563return orig;564}
565
566Vector GraphicsWindow::UnProjectPoint3(Vector p) {567p.z = p.z / (scale - p.z * SS.CameraTangent() * scale);568double w = 1 + p.z * SS.CameraTangent() * scale;569p.x *= w / scale;570p.y *= w / scale;571
572Vector orig = offset.ScaledBy(-1);573orig = orig.Plus(projRight.ScaledBy(p.x)).Plus(574projUp. ScaledBy(p.y).Plus(575projRight.Cross(projUp). ScaledBy(p.z)));576return orig;577}
578
579void GraphicsWindow::NormalizeProjectionVectors() {580if(projRight.Magnitude() < LENGTH_EPS) {581projRight = Vector::From(1, 0, 0);582}583
584Vector norm = projRight.Cross(projUp);585// If projRight and projUp somehow ended up parallel, then pick an586// arbitrary projUp normal to projRight.587if(norm.Magnitude() < LENGTH_EPS) {588norm = projRight.Normal(0);589}590projUp = norm.Cross(projRight);591
592projUp = projUp.WithMagnitude(1);593projRight = projRight.WithMagnitude(1);594}
595
596void GraphicsWindow::DrawSnapGrid(Canvas *canvas) {597if(!LockedInWorkplane()) return;598
599const Camera &camera = canvas->GetCamera();600double width = camera.width,601height = camera.height;602
603hEntity he = ActiveWorkplane();604EntityBase *wrkpl = SK.GetEntity(he),605*norm = wrkpl->Normal();606Vector n = projUp.Cross(projRight);607Vector wu, wv, wn, wp;608wp = SK.GetEntity(wrkpl->point[0])->PointGetNum();609wu = norm->NormalU();610wv = norm->NormalV();611wn = norm->NormalN();612
613double g = SS.gridSpacing;614
615double umin = VERY_POSITIVE, umax = VERY_NEGATIVE,616vmin = VERY_POSITIVE, vmax = VERY_NEGATIVE;617int a;618for(a = 0; a < 4; a++) {619// Ideally, we would just do +/- half the width and height; but620// allow some extra slop for rounding.621Vector horiz = projRight.ScaledBy((0.6*width)/scale + 2*g),622vert = projUp. ScaledBy((0.6*height)/scale + 2*g);623if(a == 2 || a == 3) horiz = horiz.ScaledBy(-1);624if(a == 1 || a == 3) vert = vert. ScaledBy(-1);625Vector tp = horiz.Plus(vert).Minus(offset);626
627// Project the point into our grid plane, normal to the screen628// (not to the grid plane). If the plane is on edge then this is629// impossible so don't try to draw the grid.630bool parallel;631Vector tpp = Vector::AtIntersectionOfPlaneAndLine(632wn, wn.Dot(wp),633tp, tp.Plus(n),634¶llel);635if(parallel) return;636
637tpp = tpp.Minus(wp);638double uu = tpp.Dot(wu),639vv = tpp.Dot(wv);640
641umin = min(uu, umin);642umax = max(uu, umax);643vmin = min(vv, vmin);644vmax = max(vv, vmax);645}646
647int i, j, i0, i1, j0, j1;648
649i0 = (int)(umin / g);650i1 = (int)(umax / g);651j0 = (int)(vmin / g);652j1 = (int)(vmax / g);653
654if(i0 > i1 || i1 - i0 > 400) return;655if(j0 > j1 || j1 - j0 > 400) return;656
657Canvas::Stroke stroke = {};658stroke.layer = Canvas::Layer::BACK;659stroke.color = Style::Color(Style::DATUM).WithAlpha(75);660stroke.unit = Canvas::Unit::PX;661stroke.width = 1.0f;662Canvas::hStroke hcs = canvas->GetStroke(stroke);663
664for(i = i0 + 1; i < i1; i++) {665canvas->DrawLine(wp.Plus(wu.ScaledBy(i*g)).Plus(wv.ScaledBy(j0*g)),666wp.Plus(wu.ScaledBy(i*g)).Plus(wv.ScaledBy(j1*g)),667hcs);668}669for(j = j0 + 1; j < j1; j++) {670canvas->DrawLine(wp.Plus(wu.ScaledBy(i0*g)).Plus(wv.ScaledBy(j*g)),671wp.Plus(wu.ScaledBy(i1*g)).Plus(wv.ScaledBy(j*g)),672hcs);673}674}
675
676void GraphicsWindow::DrawEntities(Canvas *canvas, bool persistent) {677for(Entity &e : SK.entity) {678if(persistent == (e.IsNormal() || e.IsWorkplane())) continue;679switch(SS.GW.drawOccludedAs) {680case DrawOccludedAs::VISIBLE:681e.Draw(Entity::DrawAs::OVERLAY, canvas);682break;683
684case DrawOccludedAs::STIPPLED:685e.Draw(Entity::DrawAs::HIDDEN, canvas);686/* fallthrough */687case DrawOccludedAs::INVISIBLE:688e.Draw(Entity::DrawAs::DEFAULT, canvas);689break;690}691}692}
693
694void GraphicsWindow::DrawPersistent(Canvas *canvas) {695// Draw the active group; this does stuff like the mesh and edges.696SK.GetGroup(activeGroup)->Draw(canvas);697
698// Now draw the entities that don't change with viewport.699DrawEntities(canvas, /*persistent=*/true);700
701// Draw filled paths in all groups, when those filled paths were requested702// specially by assigning a style with a fill color, or when the filled703// paths are just being filled by default. This should go last, to make704// the transparency work.705for(hGroup hg : SK.groupOrder) {706Group *g = SK.GetGroup(hg);707if(!(g->IsVisible())) continue;708g->DrawFilledPaths(canvas);709}710}
711
712void GraphicsWindow::Draw(Canvas *canvas) {713const Camera &camera = canvas->GetCamera();714
715// Nasty case when we're reloading the linked files; could be that716// we get an error, so a dialog pops up, and a message loop starts, and717// we have to get called to paint ourselves. If the sketch is screwed718// up, then we could trigger an oops trying to draw.719if(!SS.allConsistent) return;720
721if(showSnapGrid) DrawSnapGrid(canvas);722
723// Draw all the things that don't change when we rotate.724if(persistentCanvas != NULL) {725if(persistentDirty) {726persistentDirty = false;727
728persistentCanvas->Clear();729DrawPersistent(&*persistentCanvas);730persistentCanvas->Finalize();731}732
733persistentCanvas->Draw();734} else {735DrawPersistent(canvas);736}737
738// Draw the entities that do change with viewport.739DrawEntities(canvas, /*persistent=*/false);740
741// Draw the polygon errors.742if(SS.checkClosedContour) {743SK.GetGroup(activeGroup)->DrawPolyError(canvas);744}745
746// Draw the constraints747for(Constraint &c : SK.constraint) {748c.Draw(Constraint::DrawAs::DEFAULT, canvas);749}750
751// Draw areas752if(SS.showContourAreas) {753for(hGroup hg : SK.groupOrder) {754Group *g = SK.GetGroup(hg);755if(g->h != activeGroup) continue;756if(!(g->IsVisible())) continue;757g->DrawContourAreaLabels(canvas);758}759}760
761// Draw the "pending" constraint, i.e. a constraint that would be762// placed on a line that is almost horizontal or vertical.763if(SS.GW.pending.operation == Pending::DRAGGING_NEW_LINE_POINT &&764SS.GW.pending.hasSuggestion) {765Constraint c = {};766c.group = SS.GW.activeGroup;767c.workplane = SS.GW.ActiveWorkplane();768c.type = SS.GW.pending.suggestion;769c.entityA = SS.GW.pending.request.entity(0);770c.Draw(Constraint::DrawAs::DEFAULT, canvas);771}772
773Canvas::Stroke strokeAnalyze = Style::Stroke(Style::ANALYZE);774strokeAnalyze.layer = Canvas::Layer::FRONT;775Canvas::hStroke hcsAnalyze = canvas->GetStroke(strokeAnalyze);776
777// Draw the traced path, if one exists778SEdgeList tracedEdges = {};779SS.traced.path.MakeEdgesInto(&tracedEdges);780canvas->DrawEdges(tracedEdges, hcsAnalyze);781tracedEdges.Clear();782
783Canvas::Stroke strokeError = Style::Stroke(Style::DRAW_ERROR);784strokeError.layer = Canvas::Layer::FRONT;785strokeError.width = 12;786Canvas::hStroke hcsError = canvas->GetStroke(strokeError);787
788// And the naked edges, if the user did Analyze -> Show Naked Edges.789canvas->DrawEdges(SS.nakedEdges, hcsError);790
791// Then redraw whatever the mouse is hovering over, highlighted.792hover.Draw(/*isHovered=*/true, canvas);793SK.GetGroup(activeGroup)->DrawMesh(Group::DrawMeshAs::HOVERED, canvas);794
795// And finally draw the selection, same mechanism.796for(Selection *s = selection.First(); s; s = selection.NextAfter(s)) {797s->Draw(/*isHovered=*/false, canvas);798}799SK.GetGroup(activeGroup)->DrawMesh(Group::DrawMeshAs::SELECTED, canvas);800
801Canvas::Stroke strokeDatum = Style::Stroke(Style::DATUM);802strokeDatum.unit = Canvas::Unit::PX;803strokeDatum.layer = Canvas::Layer::FRONT;804strokeDatum.width = 1;805Canvas::hStroke hcsDatum = canvas->GetStroke(strokeDatum);806
807// An extra line, used to indicate the origin when rotating within the808// plane of the monitor.809if(SS.extraLine.draw) {810canvas->DrawLine(SS.extraLine.ptA, SS.extraLine.ptB, hcsDatum);811}812
813if(SS.centerOfMass.draw && !SS.centerOfMass.dirty) {814Vector p = SS.centerOfMass.position;815Vector u = camera.projRight;816Vector v = camera.projUp;817
818const double size = 10.0;819const int subdiv = 16;820double h = Style::DefaultTextHeight() / camera.scale;821std::string s =822SS.MmToStringSI(p.x) + ", " +823SS.MmToStringSI(p.y) + ", " +824SS.MmToStringSI(p.z);825canvas->DrawVectorText(s.c_str(), h,826p.Plus(u.ScaledBy((size + 5.0)/scale)).Minus(v.ScaledBy(h / 2.0)),827u, v, hcsDatum);828u = u.WithMagnitude(size / scale);829v = v.WithMagnitude(size / scale);830
831canvas->DrawLine(p.Minus(u), p.Plus(u), hcsDatum);832canvas->DrawLine(p.Minus(v), p.Plus(v), hcsDatum);833Vector prev;834for(int i = 0; i <= subdiv; i++) {835double a = (double)i / subdiv * 2.0 * PI;836Vector point = p.Plus(u.ScaledBy(cos(a))).Plus(v.ScaledBy(sin(a)));837if(i > 0) {838canvas->DrawLine(point, prev, hcsDatum);839}840prev = point;841}842}843
844// A note to indicate the origin in the just-exported file.845if(SS.justExportedInfo.draw) {846Vector p, u, v;847if(SS.justExportedInfo.showOrigin) {848p = SS.justExportedInfo.pt,849u = SS.justExportedInfo.u,850v = SS.justExportedInfo.v;851} else {852p = camera.offset.ScaledBy(-1);853u = camera.projRight;854v = camera.projUp;855}856canvas->DrawVectorText("previewing exported geometry; press Esc to return",857Style::DefaultTextHeight() / camera.scale,858p.Plus(u.ScaledBy(10/scale)).Plus(v.ScaledBy(10/scale)), u, v,859hcsDatum);860
861if(SS.justExportedInfo.showOrigin) {862Vector um = p.Plus(u.WithMagnitude(-15/scale)),863up = p.Plus(u.WithMagnitude(30/scale)),864vm = p.Plus(v.WithMagnitude(-15/scale)),865vp = p.Plus(v.WithMagnitude(30/scale));866canvas->DrawLine(um, up, hcsDatum);867canvas->DrawLine(vm, vp, hcsDatum);868canvas->DrawVectorText("(x, y) = (0, 0) for file just exported",869Style::DefaultTextHeight() / camera.scale,870p.Plus(u.ScaledBy(40/scale)).Plus(871v.ScaledBy(-(Style::DefaultTextHeight())/scale)), u, v,872hcsDatum);873}874}875}
876
877void GraphicsWindow::Paint() {878ssassert(window != NULL && canvas != NULL,879"Cannot paint without window and canvas");880
881havePainted = true;882
883Camera camera = GetCamera();884Lighting lighting = GetLighting();885
886if(!SS.ActiveGroupsOkay()) {887// Draw a different background whenever we're having solve problems.888RgbaColor bgColor = Style::Color(Style::DRAW_ERROR);889bgColor = RgbaColor::FromFloat(0.4f*bgColor.redF(),8900.4f*bgColor.greenF(),8910.4f*bgColor.blueF());892lighting.backgroundColor = bgColor;893// And show the text window, which has info to debug it894ForceTextWindowShown();895}896
897canvas->SetLighting(lighting);898canvas->SetCamera(camera);899canvas->StartFrame();900
901// Draw the 3d objects.902Draw(canvas.get());903canvas->FlushFrame();904
905// Draw the 2d UI overlay.906camera.LoadIdentity();907camera.offset.x = -(double)camera.width / 2.0;908camera.offset.y = -(double)camera.height / 2.0;909canvas->SetCamera(camera);910
911UiCanvas uiCanvas = {};912uiCanvas.canvas = canvas;913
914// If a marquee selection is in progress, then draw the selection915// rectangle, as an outline and a transparent fill.916if(pending.operation == Pending::DRAGGING_MARQUEE) {917Point2d begin = ProjectPoint(orig.marqueePoint);918uiCanvas.DrawRect((int)orig.mouse.x + (int)camera.width / 2,919(int)begin.x + (int)camera.width / 2,920(int)orig.mouse.y + (int)camera.height / 2,921(int)begin.y + (int)camera.height / 2,922/*fillColor=*/Style::Color(Style::HOVERED).WithAlpha(25),923/*outlineColor=*/Style::Color(Style::HOVERED));924}925
926// If we've had a screenshot requested, take it now, before the UI is overlaid.927if(!SS.screenshotFile.IsEmpty()) {928FILE *f = OpenFile(SS.screenshotFile, "wb");929if(!f || !canvas->ReadFrame()->WritePng(f, /*flip=*/true)) {930Error("Couldn't write to '%s'", SS.screenshotFile.raw.c_str());931}932if(f) fclose(f);933SS.screenshotFile.Clear();934}935
936// And finally the toolbar.937if(SS.showToolbar) {938canvas->SetCamera(camera);939ToolbarDraw(&uiCanvas);940}941
942canvas->FlushFrame();943canvas->FinishFrame();944canvas->Clear();945}
946
947void GraphicsWindow::Invalidate(bool clearPersistent) {948if(window) {949if(clearPersistent) {950persistentDirty = true;951}952window->Invalidate();953}954}
955