//----------------------------------------------------------------------------- // Draw a representation of an entity on-screen, in the case of curves up // to our chord tolerance, or return the distance from the user's mouse pointer // to the entity for selection. // // Copyright 2008-2013 Jonathan Westhues. //----------------------------------------------------------------------------- #include "solvespace.h" std::string Entity::DescriptionString() const { if(h.isFromRequest()) { Request *r = SK.GetRequest(h.request()); return r->DescriptionString(); } else { Group *g = SK.GetGroup(h.group()); return g->DescriptionString(); } } 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(); int i, j; for(i = 0; i < sbl->l.n; i++) { SBezier *sb = &(sbl->l.elem[i]); List lv = {}; sb->MakePwlInto(&lv); for(j = 1; j < lv.n; j++) { el->AddEdge(lv.elem[j-1], lv.elem[j], style.v, i); } lv.Clear(); } } SBezierList *Entity::GetOrGenerateBezierCurves() { if(beziers.l.n == 0) GenerateBezierCurves(&beziers); return &beziers; } SEdgeList *Entity::GetOrGenerateEdges() { if(edges.l.n != 0) { if(EXACT(edgesChordTol == SS.ChordTolMm())) return &edges; edges.l.Clear(); } if(edges.l.n == 0) GenerateEdges(&edges); edgesChordTol = SS.ChordTolMm(); return &edges; } BBox Entity::GetOrGenerateScreenBBox(bool *hasBBox) { SBezierList *sbl = GetOrGenerateBezierCurves(); // We don't bother with bounding boxes for workplanes, etc. *hasBBox = (IsPoint() || IsNormal() || sbl->l.n > 0); if(!*hasBBox) return {}; if(screenBBoxValid) return screenBBox; if(IsPoint()) { Vector proj = SS.GW.ProjectPoint3(PointGetNum()); screenBBox = BBox::From(proj, proj); } else if(IsNormal()) { Vector proj = SK.GetEntity(point[0])->PointGetNum(); screenBBox = BBox::From(proj, proj); } else if(sbl->l.n > 0) { Vector first = SS.GW.ProjectPoint3(sbl->l.elem[0].ctrl[0]); screenBBox = BBox::From(first, first); for(int i = 0; i < sbl->l.n; i++) { SBezier *sb = &sbl->l.elem[i]; for(int i = 0; i <= sb->deg; i++) { screenBBox.Include(SS.GW.ProjectPoint3(sb->ctrl[i])); } } } else ssassert(false, "Expected entity to be a point or have beziers"); screenBBoxValid = true; return screenBBox; } double Entity::GetDistance(Point2d mp) { dogd.drawing = false; dogd.mp = mp; dogd.dmin = 1e12; DrawOrGetDistance(); return dogd.dmin; } Vector Entity::GetReferencePos() { switch(type) { case Type::POINT_N_COPY: case Type::POINT_N_TRANS: case Type::POINT_N_ROT_TRANS: case Type::POINT_N_ROT_AA: case Type::POINT_IN_3D: case Type::POINT_IN_2D: return PointGetNum(); case Type::NORMAL_N_COPY: case Type::NORMAL_N_ROT: case Type::NORMAL_N_ROT_AA: case Type::NORMAL_IN_3D: case Type::NORMAL_IN_2D: case Type::WORKPLANE: case Type::CIRCLE: case Type::ARC_OF_CIRCLE: case Type::CUBIC: case Type::CUBIC_PERIODIC: case Type::TTF_TEXT: return SK.GetEntity(point[0])->PointGetNum(); 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)); } case Type::DISTANCE: case Type::DISTANCE_N_COPY: case Type::FACE_NORMAL_PT: case Type::FACE_XPROD: case Type::FACE_N_ROT_TRANS: case Type::FACE_N_TRANS: case Type::FACE_N_ROT_AA: break; } ssassert(false, "Unexpected entity type"); } bool Entity::IsStylable() const { if(IsPoint()) return false; if(IsWorkplane()) return false; if(IsNormal()) return false; return true; } bool Entity::IsVisible() const { Group *g = SK.GetGroup(group); if(g->h.v == Group::HGROUP_REFERENCES.v && IsNormal()) { // The reference normals are always shown return true; } 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(IsNormal() && !SS.GW.showNormals) return false; if(!SS.GW.showWorkplanes) { if(IsWorkplane() && !h.isFromRequest()) { if(g->h.v != SS.GW.activeGroup.v) { // The group-associated workplanes are hidden outside // their group. return false; } } } if(style.v) { Style *s = Style::Get(style); if(!s->visible) return false; } if(forceHidden) return false; return true; } void Entity::CalculateNumerical(bool forExport) { if(IsPoint()) actPoint = PointGetNum(); if(IsNormal()) actNormal = NormalGetNum(); if(type == Type::DISTANCE || type == Type::DISTANCE_N_COPY) { actDistance = DistanceGetNum(); } if(IsFace()) { actPoint = FaceGetPointNum(); Vector n = FaceGetNormalNum(); actNormal = Quaternion::From(0, n.x, n.y, n.z); } if(forExport) { // Visibility in copied linked entities follows source file actVisible = IsVisible(); } else { // Copied entities within a file are always visible actVisible = true; } } 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 // end tangents). //----------------------------------------------------------------------------- void Entity::ComputeInterpolatingSpline(SBezierList *sbl, bool periodic) const { static const int MAX_N = BandedMatrix::MAX_UNKNOWNS; int ep = extraPoints; // The number of unknowns to solve for. int n = periodic ? 3 + ep : ep; ssassert(n < MAX_N, "Too many unknowns"); // The number of on-curve points, one more than the number of segments. int pts = periodic ? 4 + ep : 2 + ep; int i, j, a; // The starting and finishing control points that define our end tangents // (if the spline isn't periodic), and the on-curve points. Vector ctrl_s = Vector::From(0, 0, 0); Vector ctrl_f = Vector::From(0, 0, 0); Vector pt[MAX_N+4]; if(periodic) { for(i = 0; i < ep + 3; i++) { pt[i] = SK.GetEntity(point[i])->PointGetNum(); } pt[i++] = SK.GetEntity(point[0])->PointGetNum(); } else { ctrl_s = SK.GetEntity(point[1])->PointGetNum(); ctrl_f = SK.GetEntity(point[ep+2])->PointGetNum(); j = 0; pt[j++] = SK.GetEntity(point[0])->PointGetNum(); for(i = 2; i <= ep + 1; i++) { pt[j++] = SK.GetEntity(point[i])->PointGetNum(); } pt[j++] = SK.GetEntity(point[ep+3])->PointGetNum(); } // The unknowns that we will be solving for, a set for each coordinate. double Xx[MAX_N], Xy[MAX_N], Xz[MAX_N]; // For a cubic Bezier section f(t) as t goes from 0 to 1, // f' (0) = 3*(P1 - P0) // f' (1) = 3*(P3 - P2) // f''(0) = 6*(P0 - 2*P1 + P2) // f''(1) = 6*(P3 - 2*P2 + P1) for(a = 0; a < 3; a++) { BandedMatrix bm = {}; bm.n = n; for(i = 0; i < n; i++) { int im, it, ip; if(periodic) { im = WRAP(i - 1, n); it = i; ip = WRAP(i + 1, n); } else { im = i; it = i + 1; ip = i + 2; } // All of these are expressed in terms of a constant part, and // of X[i-1], X[i], and X[i+1]; so let these be the four // components of that vector; Vector4 A, B, C, D, E; // The on-curve interpolated point C = Vector4::From((pt[it]).Element(a), 0, 0, 0); // control point one back, C - X[i] B = C.Plus(Vector4::From(0, 0, -1, 0)); // control point one forward, C + X[i] D = C.Plus(Vector4::From(0, 0, 1, 0)); // control point two back if(i == 0 && !periodic) { A = Vector4::From(ctrl_s.Element(a), 0, 0, 0); } else { // pt[im] + X[i-1] A = Vector4::From(pt[im].Element(a), 1, 0, 0); } // control point two forward if(i == (n - 1) && !periodic) { E = Vector4::From(ctrl_f.Element(a), 0, 0, 0); } else { // pt[ip] - X[i+1] E = Vector4::From((pt[ip]).Element(a), 0, 0, -1); } // Write the second derivatives of each segment, dropping constant Vector4 fprev_pp = (C.Minus(B.ScaledBy(2))).Plus(A), fnext_pp = (C.Minus(D.ScaledBy(2))).Plus(E), eq = fprev_pp.Minus(fnext_pp); bm.B[i] = -eq.w; if(periodic) { bm.A[i][WRAP(i-2, n)] = eq.x; bm.A[i][WRAP(i-1, n)] = eq.y; bm.A[i][i] = eq.z; } else { // The wrapping would work, except when n = 1 and everything // wraps to zero... if(i > 0) bm.A[i][i - 1] = eq.x; bm.A[i][i] = eq.y; if(i < (n-1)) bm.A[i][i + 1] = eq.z; } } bm.Solve(); double *X = (a == 0) ? Xx : (a == 1) ? Xy : Xz; memcpy(X, bm.X, n*sizeof(double)); } for(i = 0; i < pts - 1; i++) { Vector p0, p1, p2, p3; if(periodic) { p0 = pt[i]; int iw = WRAP(i - 1, n); p1 = p0.Plus(Vector::From(Xx[iw], Xy[iw], Xz[iw])); } else if(i == 0) { p0 = pt[0]; p1 = ctrl_s; } else { p0 = pt[i]; p1 = p0.Plus(Vector::From(Xx[i-1], Xy[i-1], Xz[i-1])); } if(periodic) { p3 = pt[i+1]; int iw = WRAP(i, n); p2 = p3.Minus(Vector::From(Xx[iw], Xy[iw], Xz[iw])); } else if(i == (pts - 2)) { p3 = pt[pts-1]; p2 = ctrl_f; } else { p3 = pt[i+1]; p2 = p3.Minus(Vector::From(Xx[i], Xy[i], Xz[i])); } SBezier sb = SBezier::From(p0, p1, p2, p3); sbl->l.Add(&sb); } } void Entity::GenerateBezierCurves(SBezierList *sbl) const { SBezier sb; int i = sbl->l.n; switch(type) { case Type::LINE_SEGMENT: { Vector a = SK.GetEntity(point[0])->PointGetNum(); Vector b = SK.GetEntity(point[1])->PointGetNum(); sb = SBezier::From(a, b); sb.entity = h.v; sbl->l.Add(&sb); break; } case Type::CUBIC: ComputeInterpolatingSpline(sbl, /*periodic=*/false); break; case Type::CUBIC_PERIODIC: ComputeInterpolatingSpline(sbl, /*periodic=*/true); break; case Type::CIRCLE: case Type::ARC_OF_CIRCLE: { Vector center = SK.GetEntity(point[0])->PointGetNum(); Quaternion q = SK.GetEntity(normal)->NormalGetNum(); Vector u = q.RotationU(), v = q.RotationV(); double r = CircleGetRadiusNum(); double thetaa, thetab, dtheta; if(r < LENGTH_EPS) { // If a circle or an arc gets dragged through zero radius, // then we just don't generate anything. break; } if(type == Type::CIRCLE) { thetaa = 0; thetab = 2*PI; dtheta = 2*PI; } else { ArcGetAngles(&thetaa, &thetab, &dtheta); } int i, n; if(dtheta > (3*PI/2 + 0.01)) { n = 4; } else if(dtheta > (PI + 0.01)) { n = 3; } else if(dtheta > (PI/2 + 0.01)) { n = 2; } else { n = 1; } dtheta /= n; for(i = 0; i < n; i++) { double s, c; c = cos(thetaa); s = sin(thetaa); // The start point of the curve, and the tangent vector at // that start point. Vector p0 = center.Plus(u.ScaledBy( r*c)).Plus(v.ScaledBy(r*s)), t0 = u.ScaledBy(-r*s). Plus(v.ScaledBy(r*c)); thetaa += dtheta; c = cos(thetaa); s = sin(thetaa); Vector p2 = center.Plus(u.ScaledBy( r*c)).Plus(v.ScaledBy(r*s)), t2 = u.ScaledBy(-r*s). Plus(v.ScaledBy(r*c)); // The control point must lie on both tangents. Vector p1 = Vector::AtIntersectionOfLines(p0, p0.Plus(t0), p2, p2.Plus(t2), NULL); SBezier sb = SBezier::From(p0, p1, p2); sb.weight[1] = cos(dtheta/2); sbl->l.Add(&sb); } break; } case Type::TTF_TEXT: { Vector topLeft = SK.GetEntity(point[0])->PointGetNum(); Vector botLeft = SK.GetEntity(point[1])->PointGetNum(); Vector n = Normal()->NormalN(); Vector v = topLeft.Minus(botLeft); Vector u = (v.Cross(n)).WithMagnitude(v.Magnitude()); SS.fonts.PlotString(font, str, sbl, botLeft, u, v); break; } default: // Not a problem, points and normals and such don't generate curves break; } // Record our style for all of the Beziers that we just created. for(; i < sbl->l.n; i++) { sbl->l.elem[i].auxA = style.v; } } 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; } if(!IsVisible()) return; switch(type) { case Type::POINT_N_COPY: case Type::POINT_N_TRANS: case Type::POINT_N_ROT_TRANS: case Type::POINT_N_ROT_AA: case Type::POINT_IN_3D: case Type::POINT_IN_2D: { Vector v = PointGetNum(); 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); 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; } return; } case Type::NORMAL_N_COPY: case Type::NORMAL_N_ROT: 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; } 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; } Quaternion q = NormalGetNum(); Vector tail; if(i == 0) { tail = SK.GetEntity(point[0])->PointGetNum(); if(dogd.drawing) ssglLineWidth(1); } else { // 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); } } Vector v = (q.RotationN()).WithMagnitude(50/SS.GW.scale); Vector tip = tail.Plus(v); LineDrawOrGetDistance(tail, tip); v = v.WithMagnitude(12/SS.GW.scale); Vector axis = q.RotationV(); LineDrawOrGetDistance(tip,tip.Minus(v.RotatedAbout(axis, 0.6))); LineDrawOrGetDistance(tip,tip.Minus(v.RotatedAbout(axis,-0.6))); } if(dogd.drawing) ssglDepthRangeLockToFront(false); return; } case Type::DISTANCE: case Type::DISTANCE_N_COPY: // These are used only as data structures, nothing to display. return; case Type::WORKPLANE: { Vector p; 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; Vector us = u.ScaledBy(s); Vector vs = v.ScaledBy(s); Vector pp = p.Plus (us).Plus (vs); Vector pm = p.Plus (us).Minus(vs); 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); } if(!h.isFromRequest()) { mm = mm.Plus(v.ScaledBy(70/SS.GW.scale)); mm2 = mm2.Plus(u.ScaledBy(70/SS.GW.scale)); LineDrawOrGetDistance(mm2, mm); } LineDrawOrGetDistance(pp, pm); LineDrawOrGetDistance(pm, mm2); LineDrawOrGetDistance(mp, mm); LineDrawOrGetDistance(pp, mp); 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; } return; } case Type::LINE_SEGMENT: case Type::CIRCLE: case Type::ARC_OF_CIRCLE: 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); } return; } case Type::FACE_NORMAL_PT: case Type::FACE_XPROD: case Type::FACE_N_ROT_TRANS: case Type::FACE_N_TRANS: case Type::FACE_N_ROT_AA: // Do nothing; these are drawn with the triangle mesh return; } ssassert(false, "Unexpected entity type"); }