Solvespace
863 строки · 30.1 Кб
1//-----------------------------------------------------------------------------
2// Draw a representation of an entity on-screen, in the case of curves up
3// to our chord tolerance, or return the distance from the user's mouse pointer
4// to the entity for selection.
5//
6// Copyright 2008-2013 Jonathan Westhues.
7//-----------------------------------------------------------------------------
8#include "solvespace.h"9
10std::string Entity::DescriptionString() const {11if(h.isFromRequest()) {12Request *r = SK.GetRequest(h.request());13return r->DescriptionString();14} else {15Group *g = SK.GetGroup(h.group());16return g->DescriptionString();17}18}
19
20void Entity::GenerateEdges(SEdgeList *el) {21SBezierList *sbl = GetOrGenerateBezierCurves();22
23for(int i = 0; i < sbl->l.n; i++) {24SBezier *sb = &(sbl->l[i]);25
26List<Vector> lv = {};27sb->MakePwlInto(&lv);28for(int j = 1; j < lv.n; j++) {29el->AddEdge(lv[j-1], lv[j], Style::ForEntity(h).v, i);30}31lv.Clear();32}33}
34
35SBezierList *Entity::GetOrGenerateBezierCurves() {36if(beziers.l.IsEmpty())37GenerateBezierCurves(&beziers);38return &beziers;39}
40
41SEdgeList *Entity::GetOrGenerateEdges() {42if(!edges.l.IsEmpty()) {43if(EXACT(edgesChordTol == SS.ChordTolMm()))44return &edges;45edges.l.Clear();46}47if(edges.l.IsEmpty())48GenerateEdges(&edges);49edgesChordTol = SS.ChordTolMm();50return &edges;51}
52
53BBox Entity::GetOrGenerateScreenBBox(bool *hasBBox) {54SBezierList *sbl = GetOrGenerateBezierCurves();55
56// We don't bother with bounding boxes for workplanes, etc.57*hasBBox = (IsPoint() || IsNormal() || !sbl->l.IsEmpty());58if(!*hasBBox) return {};59
60if(screenBBoxValid)61return screenBBox;62
63if(IsPoint()) {64Vector proj = SS.GW.ProjectPoint3(PointGetNum());65screenBBox = BBox::From(proj, proj);66} else if(IsNormal()) {67Vector proj = SS.GW.ProjectPoint3(SK.GetEntity(point[0])->PointGetNum());68screenBBox = BBox::From(proj, proj);69} else if(!sbl->l.IsEmpty()) {70Vector first = SS.GW.ProjectPoint3(sbl->l[0].ctrl[0]);71screenBBox = BBox::From(first, first);72for(auto &sb : sbl->l) {73for(int i = 0; i <= sb.deg; ++i) { screenBBox.Include(SS.GW.ProjectPoint3(sb.ctrl[i])); }74}75} else76ssassert(false, "Expected entity to be a point or have beziers");77
78screenBBoxValid = true;79return screenBBox;80}
81
82void Entity::GetReferencePoints(std::vector<Vector> *refs) {83switch(type) {84case Type::POINT_N_COPY:85case Type::POINT_N_TRANS:86case Type::POINT_N_ROT_TRANS:87case Type::POINT_N_ROT_AA:88case Type::POINT_N_ROT_AXIS_TRANS:89case Type::POINT_IN_3D:90case Type::POINT_IN_2D:91refs->push_back(PointGetDrawNum());92break;93
94case Type::NORMAL_N_COPY:95case Type::NORMAL_N_ROT:96case Type::NORMAL_N_ROT_AA:97case Type::NORMAL_IN_3D:98case Type::NORMAL_IN_2D:99case Type::WORKPLANE:100case Type::CIRCLE:101case Type::ARC_OF_CIRCLE:102case Type::CUBIC:103case Type::CUBIC_PERIODIC:104case Type::TTF_TEXT:105case Type::IMAGE:106refs->push_back(SK.GetEntity(point[0])->PointGetDrawNum());107break;108
109case Type::LINE_SEGMENT: {110Vector a = SK.GetEntity(point[0])->PointGetDrawNum(),111b = SK.GetEntity(point[1])->PointGetDrawNum();112refs->push_back(b.Plus(a.Minus(b).ScaledBy(0.5)));113break;114}115
116case Type::DISTANCE:117case Type::DISTANCE_N_COPY:118case Type::FACE_NORMAL_PT:119case Type::FACE_XPROD:120case Type::FACE_N_ROT_TRANS:121case Type::FACE_N_TRANS:122case Type::FACE_N_ROT_AA:123case Type::FACE_ROT_NORMAL_PT:124case Type::FACE_N_ROT_AXIS_TRANS:125break;126}127}
128
129int Entity::GetPositionOfPoint(const Camera &camera, Point2d p) {130int position;131
132ObjectPicker canvas = {};133canvas.camera = camera;134canvas.point = p;135canvas.minDistance = 1e12;136Draw(DrawAs::DEFAULT, &canvas);137position = canvas.position;138canvas.Clear();139
140return position;141}
142
143bool Entity::IsStylable() const {144if(IsPoint()) return false;145if(IsWorkplane()) return false;146if(IsNormal()) return false;147return true;148}
149
150bool Entity::IsVisible() const {151Group *g = SK.GetGroup(group);152
153if(g->h == Group::HGROUP_REFERENCES && IsNormal()) {154// The reference normals are always shown155return true;156}157if(!(g->IsVisible())) return false;158
159if(IsPoint() && !SS.GW.showPoints) return false;160if(IsNormal() && !SS.GW.showNormals) return false;161if(construction && !SS.GW.showConstruction) return false;162
163if(!SS.GW.showWorkplanes) {164if(IsWorkplane() && !h.isFromRequest()) {165if(g->h != SS.GW.activeGroup) {166// The group-associated workplanes are hidden outside167// their group.168return false;169}170}171}172
173if(style.v) {174Style *s = Style::Get(style);175if(!s->visible) return false;176}177
178if(forceHidden) return false;179
180return true;181}
182
183static bool PtCanDrag(hEntity pt) {184Entity* p = SK.GetEntity(pt);185// a numeric copy can not move186if(p->type == Entity::Type::POINT_N_COPY) return false;187// these transforms applied zero times can not be moved188if(((p->type == Entity::Type::POINT_N_TRANS) ||189(p->type == Entity::Type::POINT_N_ROT_AA) ||190(p->type == Entity::Type::POINT_N_ROT_AXIS_TRANS))191&& (p->timesApplied == 0)) return false;192return true;193}
194
195// entities that were created via some copy types will not be
196// draggable with the mouse. We identify the undraggables here
197bool Entity::CanBeDragged() const {198if(IsPoint()) {199if(!PtCanDrag(h))200return false;201// are we constrained pt-on-point from a previous group?202for(const Constraint &cc : SK.constraint) {203if(cc.group == group && cc.type == ConstraintBase::Type::POINTS_COINCIDENT) {204if(cc.ptA == h) {205if((SK.GetEntity(cc.ptB)->group < group)206|| (!PtCanDrag(cc.ptB)))207return false;208}209if(cc.ptB == h) {210if((SK.GetEntity(cc.ptA)->group < group)211|| (!PtCanDrag(cc.ptA)))212return false;213}214}215}216}217// for these types of entities the first point will indicate draggability218if(HasEndpoints() || type == Entity::Type::CIRCLE) {219return PtCanDrag(point[0]);220}221// if we're not certain it can't be dragged then default to true222return true;223}
224
225void Entity::CalculateNumerical(bool forExport) {226if(IsPoint()) actPoint = PointGetNum();227if(IsNormal()) actNormal = NormalGetNum();228if(type == Type::DISTANCE || type == Type::DISTANCE_N_COPY) {229actDistance = DistanceGetNum();230}231if(IsFace()) {232actPoint = FaceGetPointNum();233Vector n = FaceGetNormalNum();234actNormal = Quaternion::From(0, n.x, n.y, n.z);235}236if(forExport) {237// Visibility in copied linked entities follows source file238actVisible = IsVisible();239} else {240// Copied entities within a file are always visible241actVisible = true;242}243}
244
245//-----------------------------------------------------------------------------
246// Compute a cubic, second derivative continuous, interpolating spline. Same
247// routine for periodic splines (in a loop) or open splines (with specified
248// end tangents).
249//-----------------------------------------------------------------------------
250void Entity::ComputeInterpolatingSpline(SBezierList *sbl, bool periodic) const {251static const int MAX_N = BandedMatrix::MAX_UNKNOWNS;252int ep = extraPoints;253
254// The number of unknowns to solve for.255int n = periodic ? 3 + ep : ep;256ssassert(n < MAX_N, "Too many unknowns");257// The number of on-curve points, one more than the number of segments.258int pts = periodic ? 4 + ep : 2 + ep;259
260int i, j, a;261
262// The starting and finishing control points that define our end tangents263// (if the spline isn't periodic), and the on-curve points.264Vector ctrl_s = Vector::From(0, 0, 0);265Vector ctrl_f = Vector::From(0, 0, 0);266Vector pt[MAX_N+4];267if(periodic) {268for(i = 0; i < ep + 3; i++) {269pt[i] = SK.GetEntity(point[i])->PointGetNum();270}271pt[i++] = SK.GetEntity(point[0])->PointGetNum();272} else {273ctrl_s = SK.GetEntity(point[1])->PointGetNum();274ctrl_f = SK.GetEntity(point[ep+2])->PointGetNum();275j = 0;276pt[j++] = SK.GetEntity(point[0])->PointGetNum();277for(i = 2; i <= ep + 1; i++) {278pt[j++] = SK.GetEntity(point[i])->PointGetNum();279}280pt[j++] = SK.GetEntity(point[ep+3])->PointGetNum();281}282
283// The unknowns that we will be solving for, a set for each coordinate.284double Xx[MAX_N], Xy[MAX_N], Xz[MAX_N];285// For a cubic Bezier section f(t) as t goes from 0 to 1,286// f' (0) = 3*(P1 - P0)287// f' (1) = 3*(P3 - P2)288// f''(0) = 6*(P0 - 2*P1 + P2)289// f''(1) = 6*(P3 - 2*P2 + P1)290for(a = 0; a < 3; a++) {291BandedMatrix bm = {};292bm.n = n;293
294for(i = 0; i < n; i++) {295int im, it, ip;296if(periodic) {297im = WRAP(i - 1, n);298it = i;299ip = WRAP(i + 1, n);300} else {301im = i;302it = i + 1;303ip = i + 2;304}305// All of these are expressed in terms of a constant part, and306// of X[i-1], X[i], and X[i+1]; so let these be the four307// components of that vector;308Vector4 A, B, C, D, E;309// The on-curve interpolated point310C = Vector4::From((pt[it]).Element(a), 0, 0, 0);311// control point one back, C - X[i]312B = C.Plus(Vector4::From(0, 0, -1, 0));313// control point one forward, C + X[i]314D = C.Plus(Vector4::From(0, 0, 1, 0));315// control point two back316if(i == 0 && !periodic) {317A = Vector4::From(ctrl_s.Element(a), 0, 0, 0);318} else {319// pt[im] + X[i-1]320A = Vector4::From(pt[im].Element(a), 1, 0, 0);321}322// control point two forward323if(i == (n - 1) && !periodic) {324E = Vector4::From(ctrl_f.Element(a), 0, 0, 0);325} else {326// pt[ip] - X[i+1]327E = Vector4::From((pt[ip]).Element(a), 0, 0, -1);328}329// Write the second derivatives of each segment, dropping constant330Vector4 fprev_pp = (C.Minus(B.ScaledBy(2))).Plus(A),331fnext_pp = (C.Minus(D.ScaledBy(2))).Plus(E),332eq = fprev_pp.Minus(fnext_pp);333
334bm.B[i] = -eq.w;335if(periodic) {336bm.A[i][WRAP(i-2, n)] = eq.x;337bm.A[i][WRAP(i-1, n)] = eq.y;338bm.A[i][i] = eq.z;339} else {340// The wrapping would work, except when n = 1 and everything341// wraps to zero...342if(i > 0) {343bm.A[i][i - 1] = eq.x;344}345bm.A[i][i] = eq.y;346if(i < (n-1)) {347bm.A[i][i + 1] = eq.z;348}349}350}351bm.Solve();352double *X = (a == 0) ? Xx :353(a == 1) ? Xy :354Xz;355memcpy(X, bm.X, n*sizeof(double));356}357
358for(i = 0; i < pts - 1; i++) {359Vector p0, p1, p2, p3;360if(periodic) {361p0 = pt[i];362int iw = WRAP(i - 1, n);363p1 = p0.Plus(Vector::From(Xx[iw], Xy[iw], Xz[iw]));364} else if(i == 0) {365p0 = pt[0];366p1 = ctrl_s;367} else {368p0 = pt[i];369p1 = p0.Plus(Vector::From(Xx[i-1], Xy[i-1], Xz[i-1]));370}371if(periodic) {372p3 = pt[i+1];373int iw = WRAP(i, n);374p2 = p3.Minus(Vector::From(Xx[iw], Xy[iw], Xz[iw]));375} else if(i == (pts - 2)) {376p3 = pt[pts-1];377p2 = ctrl_f;378} else {379p3 = pt[i+1];380p2 = p3.Minus(Vector::From(Xx[i], Xy[i], Xz[i]));381}382SBezier sb = SBezier::From(p0, p1, p2, p3);383sbl->l.Add(&sb);384}385}
386
387void Entity::GenerateBezierCurves(SBezierList *sbl) const {388SBezier sb;389
390int i = sbl->l.n;391
392switch(type) {393case Type::LINE_SEGMENT: {394Vector a = SK.GetEntity(point[0])->PointGetNum();395Vector b = SK.GetEntity(point[1])->PointGetNum();396sb = SBezier::From(a, b);397sb.entity = h.v;398sbl->l.Add(&sb);399break;400}401case Type::CUBIC:402ComputeInterpolatingSpline(sbl, /*periodic=*/false);403break;404
405case Type::CUBIC_PERIODIC:406ComputeInterpolatingSpline(sbl, /*periodic=*/true);407break;408
409case Type::CIRCLE:410case Type::ARC_OF_CIRCLE: {411Vector center = SK.GetEntity(point[0])->PointGetNum();412Quaternion q = SK.GetEntity(normal)->NormalGetNum();413Vector u = q.RotationU(), v = q.RotationV();414double r = CircleGetRadiusNum();415double thetaa, thetab, dtheta;416
417if(r < LENGTH_EPS) {418// If a circle or an arc gets dragged through zero radius,419// then we just don't generate anything.420break;421}422
423if(type == Type::CIRCLE) {424thetaa = 0;425thetab = 2*PI;426dtheta = 2*PI;427} else {428ArcGetAngles(&thetaa, &thetab, &dtheta);429}430int i, n;431if(dtheta > (3*PI/2 + 0.01)) {432n = 4;433} else if(dtheta > (PI + 0.01)) {434n = 3;435} else if(dtheta > (PI/2 + 0.01)) {436n = 2;437} else {438n = 1;439}440dtheta /= n;441
442for(i = 0; i < n; i++) {443double s, c;444
445c = cos(thetaa);446s = sin(thetaa);447// The start point of the curve, and the tangent vector at448// that start point.449Vector p0 = center.Plus(u.ScaledBy( r*c)).Plus(v.ScaledBy(r*s)),450t0 = u.ScaledBy(-r*s). Plus(v.ScaledBy(r*c));451
452thetaa += dtheta;453
454c = cos(thetaa);455s = sin(thetaa);456Vector p2 = center.Plus(u.ScaledBy( r*c)).Plus(v.ScaledBy(r*s)),457t2 = u.ScaledBy(-r*s). Plus(v.ScaledBy(r*c));458
459// The control point must lie on both tangents.460Vector p1 = Vector::AtIntersectionOfLines(p0, p0.Plus(t0),461p2, p2.Plus(t2),462NULL);463
464SBezier sb = SBezier::From(p0, p1, p2);465sb.weight[1] = cos(dtheta/2);466sbl->l.Add(&sb);467}468break;469}470
471case Type::TTF_TEXT: {472Vector topLeft = SK.GetEntity(point[0])->PointGetNum();473Vector botLeft = SK.GetEntity(point[1])->PointGetNum();474Vector n = Normal()->NormalN();475Vector v = topLeft.Minus(botLeft);476Vector u = (v.Cross(n)).WithMagnitude(v.Magnitude());477
478// `extraPoints` is storing kerning boolean479SS.fonts.PlotString(font, str, sbl, extraPoints, botLeft, u, v);480break;481}482
483default:484// Not a problem, points and normals and such don't generate curves485break;486}487
488// Record our style for all of the Beziers that we just created.489for(; i < sbl->l.n; i++) {490sbl->l[i].auxA = Style::ForEntity(h).v;491}492}
493
494bool Entity::ShouldDrawExploded() const {495return SK.GetGroup(group)->ShouldDrawExploded();496}
497
498Vector Entity::ExplodeOffset() const {499if(ShouldDrawExploded() && workplane.v != 0) {500int requestIdx = SK.GetRequest(h.request())->groupRequestIndex;501double offset = SS.explodeDistance * (requestIdx + 1);502return SK.GetEntity(workplane)->Normal()->NormalN().ScaledBy(offset);503} else {504return Vector::From(0, 0, 0);505}506}
507
508Vector Entity::PointGetDrawNum() const {509// As per EntityBase::PointGetNum but specifically for when drawing/rendering the point510// (and not when solving), so we can potentially draw it somewhere different511return PointGetNum().Plus(ExplodeOffset());512}
513
514void Entity::Draw(DrawAs how, Canvas *canvas) {515if(!(how == DrawAs::HOVERED || how == DrawAs::SELECTED) &&516!IsVisible()) return;517
518int zIndex;519if(IsPoint()) {520zIndex = 6;521} else if(how == DrawAs::HIDDEN) {522zIndex = 2;523} else if(group != SS.GW.activeGroup) {524zIndex = 3;525} else {526zIndex = 5;527}528
529hStyle hs;530if(IsPoint()) {531hs.v = Style::DATUM;532} else if(IsNormal() || type == Type::WORKPLANE) {533hs.v = Style::NORMALS;534} else {535hs = Style::ForEntity(h);536if (hs.v == Style::CONSTRUCTION) {537zIndex = 4;538}539}540
541Canvas::Stroke stroke = Style::Stroke(hs);542switch(how) {543case DrawAs::DEFAULT:544stroke.layer = Canvas::Layer::NORMAL;545break;546
547case DrawAs::OVERLAY:548stroke.layer = Canvas::Layer::FRONT;549break;550
551case DrawAs::HIDDEN:552stroke.layer = Canvas::Layer::OCCLUDED;553stroke.stipplePattern = Style::PatternType({ Style::HIDDEN_EDGE });554stroke.stippleScale = Style::Get({ Style::HIDDEN_EDGE })->stippleScale;555break;556
557case DrawAs::HOVERED:558stroke.layer = Canvas::Layer::FRONT;559stroke.color = Style::Color(Style::HOVERED);560break;561
562case DrawAs::SELECTED:563stroke.layer = Canvas::Layer::FRONT;564stroke.color = Style::Color(Style::SELECTED);565break;566}567stroke.zIndex = zIndex;568Canvas::hStroke hcs = canvas->GetStroke(stroke);569
570switch(type) {571case Type::POINT_N_COPY:572case Type::POINT_N_TRANS:573case Type::POINT_N_ROT_TRANS:574case Type::POINT_N_ROT_AA:575case Type::POINT_N_ROT_AXIS_TRANS:576case Type::POINT_IN_3D:577case Type::POINT_IN_2D: {578if(how == DrawAs::HIDDEN) return;579
580// If we're analyzing the sketch to show the degrees of freedom,581// then we draw big colored squares over the points that are582// free to move.583bool free = false;584if(type == Type::POINT_IN_3D) {585Param *px = SK.GetParam(param[0]),586*py = SK.GetParam(param[1]),587*pz = SK.GetParam(param[2]);588
589free = px->free || py->free || pz->free;590} else if(type == Type::POINT_IN_2D) {591Param *pu = SK.GetParam(param[0]),592*pv = SK.GetParam(param[1]);593
594free = pu->free || pv->free;595}596
597Canvas::Stroke pointStroke = {};598pointStroke.layer = (free) ? Canvas::Layer::FRONT : stroke.layer;599pointStroke.zIndex = stroke.zIndex;600pointStroke.color = stroke.color;601pointStroke.width = 7.0;602pointStroke.unit = Canvas::Unit::PX;603Canvas::hStroke hcsPoint = canvas->GetStroke(pointStroke);604
605Vector p = PointGetDrawNum();606if(free) {607Canvas::Stroke analyzeStroke = Style::Stroke(Style::ANALYZE);608analyzeStroke.width = 14.0;609analyzeStroke.layer = Canvas::Layer::FRONT;610Canvas::hStroke hcsAnalyze = canvas->GetStroke(analyzeStroke);611
612canvas->DrawPoint(p, hcsAnalyze);613}614
615canvas->DrawPoint(p, hcsPoint);616return;617}618
619case Type::NORMAL_N_COPY:620case Type::NORMAL_N_ROT:621case Type::NORMAL_N_ROT_AA:622case Type::NORMAL_IN_3D:623case Type::NORMAL_IN_2D: {624const Camera &camera = canvas->GetCamera();625
626if(how == DrawAs::HIDDEN) return;627
628for(int i = 0; i < 2; i++) {629bool asReference = (i == 1);630if(asReference) {631if(!h.request().IsFromReferences()) continue;632} else {633if(!SK.GetGroup(group)->IsVisible() || !SS.GW.showNormals) continue;634}635
636stroke.layer = (asReference) ? Canvas::Layer::FRONT : Canvas::Layer::NORMAL;637if(how != DrawAs::HOVERED && how != DrawAs::SELECTED) {638// Always draw the x, y, and z axes in red, green, and blue;639// brighter for the ones at the bottom left of the screen,640// dimmer for the ones at the model origin.641hRequest hr = h.request();642uint8_t luma = (asReference) ? 255 : 100;643if(hr == Request::HREQUEST_REFERENCE_XY) {644stroke.color = RgbaColor::From(0, 0, luma);645} else if(hr == Request::HREQUEST_REFERENCE_YZ) {646stroke.color = RgbaColor::From(luma, 0, 0);647} else if(hr == Request::HREQUEST_REFERENCE_ZX) {648stroke.color = RgbaColor::From(0, luma, 0);649}650}651
652Quaternion q = NormalGetNum();653Vector tail;654if(asReference) {655// Draw an extra copy of the x, y, and z axes, that's656// always in the corner of the view and at the front.657// So those are always available, perhaps useful.658stroke.width = 2;659double s = camera.scale;660double h = 60 - camera.height / 2.0;661double w = 60 - camera.width / 2.0;662// Shift the axis to the right if they would overlap with the toolbar.663if(SS.showToolbar) {664if(h + 30 > -(32*18 + 3*16 + 8) / 2)665w += 60;666}667tail = camera.projRight.ScaledBy(w/s).Plus(668camera.projUp. ScaledBy(h/s)).Minus(camera.offset);669} else {670tail = SK.GetEntity(point[0])->PointGetDrawNum();671}672tail = camera.AlignToPixelGrid(tail);673
674hcs = canvas->GetStroke(stroke);675Vector v = (q.RotationN()).WithMagnitude(50.0 / camera.scale);676Vector tip = tail.Plus(v);677canvas->DrawLine(tail, tip, hcs);678
679v = v.WithMagnitude(12.0 / camera.scale);680Vector axis = q.RotationV();681canvas->DrawLine(tip, tip.Minus(v.RotatedAbout(axis, 0.6)), hcs);682canvas->DrawLine(tip, tip.Minus(v.RotatedAbout(axis, -0.6)), hcs);683
684if(type == Type::NORMAL_IN_3D) {685Param *nw = SK.GetParam(param[0]),686*nx = SK.GetParam(param[1]),687*ny = SK.GetParam(param[2]),688*nz = SK.GetParam(param[3]);689
690if(nw->free || nx->free || ny->free || nz->free) {691Canvas::Stroke analyzeStroke = Style::Stroke(Style::ANALYZE);692analyzeStroke.layer = Canvas::Layer::FRONT;693Canvas::hStroke hcsAnalyze = canvas->GetStroke(analyzeStroke);694canvas->DrawLine(tail, tip, hcsAnalyze);695}696}697}698return;699}700
701case Type::DISTANCE:702case Type::DISTANCE_N_COPY:703// These are used only as data structures, nothing to display.704return;705
706case Type::WORKPLANE: {707const Camera &camera = canvas->GetCamera();708
709Vector p = SK.GetEntity(point[0])->PointGetNum();710p = camera.AlignToPixelGrid(p);711
712Vector u = Normal()->NormalU();713Vector v = Normal()->NormalV();714
715double s = (std::min(camera.width, camera.height)) * 0.45 / camera.scale;716
717Vector us = u.ScaledBy(s);718Vector vs = v.ScaledBy(s);719
720Vector pp = p.Plus (us).Plus (vs);721Vector pm = p.Plus (us).Minus(vs);722Vector mm = p.Minus(us).Minus(vs), mm2 = mm;723Vector mp = p.Minus(us).Plus (vs);724
725Canvas::Stroke strokeBorder = stroke;726strokeBorder.zIndex -= 3;727strokeBorder.stipplePattern = StipplePattern::SHORT_DASH;728strokeBorder.stippleScale = 8.0;729Canvas::hStroke hcsBorder = canvas->GetStroke(strokeBorder);730
731double textHeight = Style::TextHeight(hs) / camera.scale;732
733if(!h.isFromRequest()) {734mm = mm.Plus(v.ScaledBy(textHeight * 4.7));735mm2 = mm2.Plus(u.ScaledBy(textHeight * 4.7));736canvas->DrawLine(mm2, mm, hcsBorder);737}738canvas->DrawLine(pp, pm, hcsBorder);739canvas->DrawLine(mm2, pm, hcsBorder);740canvas->DrawLine(mm, mp, hcsBorder);741canvas->DrawLine(pp, mp, hcsBorder);742
743Vector o = mm2.Plus(u.ScaledBy(3.0 / camera.scale)).Plus(744v.ScaledBy(3.0 / camera.scale));745std::string shortDesc = DescriptionString().substr(5);746canvas->DrawVectorText(shortDesc, textHeight, o, u, v, hcs);747return;748}749
750case Type::LINE_SEGMENT:751case Type::CIRCLE:752case Type::ARC_OF_CIRCLE:753case Type::CUBIC:754case Type::CUBIC_PERIODIC:755case Type::TTF_TEXT: {756// Generate the rational polynomial curves, then piecewise linearize757// them, and display those.758// Calculating the draw offset, if necessary.759const bool shouldExplode = ShouldDrawExploded();760Vector explodeOffset;761SBezierList offsetBeziers = {};762SBezierList *beziers = GetOrGenerateBezierCurves();763if(shouldExplode) {764explodeOffset = ExplodeOffset();765for(const SBezier& b : beziers->l) {766SBezier offset = b.TransformedBy(explodeOffset, Quaternion::IDENTITY, 1.0);767offsetBeziers.l.Add(&offset);768}769beziers = &offsetBeziers;770}771
772SEdgeList *edges = nullptr;773SEdgeList offsetEdges = {};774
775if(!canvas->DrawBeziers(*beziers, hcs)) {776edges = GetOrGenerateEdges();777if(shouldExplode) {778for(const SEdge &e : edges->l) {779offsetEdges.AddEdge(e.a.Plus(explodeOffset), e.b.Plus(explodeOffset), e.auxA, e.auxB, e.tag);780}781edges = &offsetEdges;782}783canvas->DrawEdges(*edges, hcs);784}785if(type == Type::CIRCLE) {786Entity *dist = SK.GetEntity(distance);787if(dist->type == Type::DISTANCE) {788Param *p = SK.GetParam(dist->param[0]);789if(p->free) {790Canvas::Stroke analyzeStroke = Style::Stroke(Style::ANALYZE);791analyzeStroke.layer = Canvas::Layer::FRONT;792Canvas::hStroke hcsAnalyze = canvas->GetStroke(analyzeStroke);793if(!canvas->DrawBeziers(*beziers, hcsAnalyze)) {794canvas->DrawEdges(*edges, hcsAnalyze);795}796}797}798}799offsetBeziers.Clear();800offsetEdges.Clear();801return;802}803case Type::IMAGE: {804Canvas::Fill fill = {};805std::shared_ptr<Pixmap> pixmap;806switch(how) {807case DrawAs::HIDDEN: return;808
809case DrawAs::HOVERED: {810fill.color = Style::Color(Style::HOVERED).WithAlpha(180);811fill.pattern = Canvas::FillPattern::CHECKERED_A;812fill.zIndex = 2;813break;814}815
816case DrawAs::SELECTED: {817fill.color = Style::Color(Style::SELECTED).WithAlpha(180);818fill.pattern = Canvas::FillPattern::CHECKERED_B;819fill.zIndex = 1;820break;821}822
823default:824fill.color = RgbaColor::FromFloat(1.0f, 1.0f, 1.0f);825pixmap = SS.images[file];826break;827}828
829Canvas::hFill hf = canvas->GetFill(fill);830Vector v[4] = {};831for(int i = 0; i < 4; i++) {832v[i] = SK.GetEntity(point[i])->PointGetDrawNum();833}834Vector iu = v[3].Minus(v[0]);835Vector iv = v[1].Minus(v[0]);836
837if(how == DrawAs::DEFAULT && pixmap == NULL) {838Canvas::Stroke stroke = Style::Stroke(Style::DRAW_ERROR);839stroke.color = stroke.color.WithAlpha(50);840Canvas::hStroke hs = canvas->GetStroke(stroke);841canvas->DrawLine(v[0], v[2], hs);842canvas->DrawLine(v[1], v[3], hs);843for(int i = 0; i < 4; i++) {844canvas->DrawLine(v[i], v[(i + 1) % 4], hs);845}846} else {847canvas->DrawPixmap(pixmap, v[0], iu, iv,848Point2d::From(0.0, 0.0), Point2d::From(1.0, 1.0), hf);849}850}851
852case Type::FACE_NORMAL_PT:853case Type::FACE_XPROD:854case Type::FACE_N_ROT_TRANS:855case Type::FACE_N_TRANS:856case Type::FACE_N_ROT_AA:857case Type::FACE_ROT_NORMAL_PT:858case Type::FACE_N_ROT_AXIS_TRANS:859// Do nothing; these are drawn with the triangle mesh860return;861}862ssassert(false, "Unexpected entity type");863}
864