void Constraint::DrawOrGetDistance(Vector *labelPos) { if(!SS.GW.showConstraints) return; Group *g = SK.GetGroup(group); // If the group is hidden, then the constraints are hidden and not // able to be selected. if(!(g->visible)) return; // And likewise if the group is not the active group; except for comments // with an assigned style. if(g->h.v != SS.GW.activeGroup.v && !(type == COMMENT && disp.style.v)) { return; } if(disp.style.v) { Style *s = Style::Get(disp.style); if(!s->visible) 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); switch(type) { case PT_PT_DISTANCE: { Vector ap = SK.GetEntity(ptA)->PointGetNum(); Vector bp = SK.GetEntity(ptB)->PointGetNum(); if(workplane.v != Entity::FREE_IN_3D.v) { DoProjectedPoint(&ap); DoProjectedPoint(&bp); } Vector ref = ((ap.Plus(bp)).ScaledBy(0.5)).Plus(disp.offset); DoLineWithArrows(ref, ap, bp, false); DoLabel(ref, labelPos, gr, gu); break; } case PROJ_PT_DISTANCE: { 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); pp = pp.WithMagnitude(1); double d = dp.Dot(pp); Vector bpp = ap.Plus(pp.ScaledBy(d)); StippledLine(ap, bpp); StippledLine(bp, bpp); DoLineWithArrows(ref, ap, bpp, false); DoLabel(ref, labelPos, gr, gu); break; } case PT_FACE_DISTANCE: case PT_PLANE_DISTANCE: { Vector pt = SK.GetEntity(ptA)->PointGetNum(); Entity *enta = SK.GetEntity(entityA); Vector n, p; if(type == PT_PLANE_DISTANCE) { n = enta->Normal()->NormalN(); p = enta->WorkplaneGetOffset(); } else { n = enta->FaceGetNormalNum(); p = enta->FaceGetPointNum(); } double d = (p.Minus(pt)).Dot(n); Vector closest = pt.Plus(n.WithMagnitude(d)); Vector ref = ((closest.Plus(pt)).ScaledBy(0.5)).Plus(disp.offset); if(!pt.Equals(closest)) { DoLineWithArrows(ref, pt, closest, true); } DoLabel(ref, labelPos, gr, gu); break; } case PT_LINE_DISTANCE: { Vector pt = SK.GetEntity(ptA)->PointGetNum(); Entity *line = SK.GetEntity(entityA); Vector lA = SK.GetEntity(line->point[0])->PointGetNum(); Vector lB = SK.GetEntity(line->point[1])->PointGetNum(); Vector dl = lB.Minus(lA); if(workplane.v != Entity::FREE_IN_3D.v) { lA = lA.ProjectInto(workplane); lB = lB.ProjectInto(workplane); DoProjectedPoint(&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); DoLabel(ref, labelPos, gr, gu); if(!pt.Equals(closest)) { DoLineWithArrows(ref, pt, closest, true); } if(workplane.v != Entity::FREE_IN_3D.v) { // Draw the projection marker from the closest point on the // projected line to the projected point on the real line. Vector lAB = (lA.Minus(lB)); double t = (lA.Minus(closest)).DivPivoting(lAB); Vector lA = SK.GetEntity(line->point[0])->PointGetNum(); Vector lB = SK.GetEntity(line->point[1])->PointGetNum(); Vector c2 = (lA.ScaledBy(1-t)).Plus(lB.ScaledBy(t)); DoProjectedPoint(&c2); } break; } case DIAMETER: { Entity *circle = SK.GetEntity(entityA); Vector center = SK.GetEntity(circle->point[0])->PointGetNum(); Quaternion q = SK.GetEntity(circle->normal)->NormalGetNum(); Vector n = q.RotationN().WithMagnitude(1); double r = circle->CircleGetRadiusNum(); 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))); Vector mark = ref.Minus(center); mark = mark.WithMagnitude(mark.Magnitude()-r); DoLineTrimmedAgainstBox(ref, ref, ref.Minus(mark)); Vector topLeft; DoLabel(ref, &topLeft, gr, gu); if(labelPos) *labelPos = topLeft; // Show this as diameter or radius? if(!other) { // Draw the diameter symbol Vector dc = topLeft; dc = dc.Plus(gu.WithMagnitude(5/SS.GW.scale)); dc = dc.Plus(gr.WithMagnitude(9/SS.GW.scale)); double dr = 5/SS.GW.scale; double theta, dtheta = (2*PI)/12; for(theta = 0; theta < 2*PI-0.01; theta += dtheta) { LineDrawOrGetDistance( dc.Plus(gu.WithMagnitude(cos(theta)*dr)).Plus( gr.WithMagnitude(sin(theta)*dr)), dc.Plus(gu.WithMagnitude(cos(theta+dtheta)*dr)).Plus( gr.WithMagnitude(sin(theta+dtheta)*dr))); } theta = 25*(PI/180); dr *= 1.7; dtheta = PI; LineDrawOrGetDistance( dc.Plus(gu.WithMagnitude(cos(theta)*dr)).Plus( gr.WithMagnitude(sin(theta)*dr)), dc.Plus(gu.WithMagnitude(cos(theta+dtheta)*dr)).Plus( gr.WithMagnitude(sin(theta+dtheta)*dr))); } break; } case POINTS_COINCIDENT: { if(!dogd.drawing) { for(int i = 0; i < 2; i++) { Vector p = SK.GetEntity(i == 0 ? ptA : ptB)-> PointGetNum(); 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); dogd.refp = p; } break; } if(dogd.drawing) { // 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); // 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()); // and scale the constraint color to have the same magnitude as // 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(); } } } break; } case PT_ON_CIRCLE: case PT_ON_LINE: case PT_ON_FACE: case PT_IN_PLANE: { double s = 8/SS.GW.scale; Vector p = SK.GetEntity(ptA)->PointGetNum(); Vector r, d; if(type == PT_ON_FACE) { Vector n = SK.GetEntity(entityA)->FaceGetNormalNum(); r = n.Normal(0); d = n.Normal(1); } else if(type == PT_IN_PLANE) { EntityBase *n = SK.GetEntity(entityA)->Normal(); r = n->NormalU(); d = n->NormalV(); } else { r = gr; d = gu; 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)); break; } case WHERE_DRAGGED: { Vector p = SK.GetEntity(ptA)->PointGetNum(), 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)); // 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); u = u.RotatedAbout(p, gn, PI/2); ur = ur.RotatedAbout(p, gn, PI/2); uu = uu.RotatedAbout(p, gn, PI/2); } break; } case SAME_ORIENTATION: { 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 p = SK.GetEntity(e->point[0])->PointGetNum(); p = p.Plus(n.WithMagnitude(10/SS.GW.scale)); LineDrawOrGetDistance(p.Plus(u), p.Minus(u).Plus(n)); LineDrawOrGetDistance(p.Minus(u), p.Plus(u).Plus(n)); } break; } case EQUAL_ANGLE: { Vector ref; Entity *a = SK.GetEntity(entityA); Entity *b = SK.GetEntity(entityB); Entity *c = SK.GetEntity(entityC); Entity *d = SK.GetEntity(entityD); Vector a0 = a->VectorGetRefPoint(); Vector b0 = b->VectorGetRefPoint(); Vector c0 = c->VectorGetRefPoint(); Vector d0 = d->VectorGetRefPoint(); Vector da = a->VectorGetNum(); Vector db = b->VectorGetNum(); Vector dc = c->VectorGetNum(); Vector dd = d->VectorGetNum(); if(other) da = da.ScaledBy(-1); DoArcForAngle(a0, da, b0, db, da.WithMagnitude(40/SS.GW.scale), &ref); DoArcForAngle(c0, dc, d0, dd, dc.WithMagnitude(40/SS.GW.scale), &ref); break; } case ANGLE: { Entity *a = SK.GetEntity(entityA); Entity *b = SK.GetEntity(entityB); Vector a0 = a->VectorGetRefPoint(); Vector b0 = b->VectorGetRefPoint(); Vector da = a->VectorGetNum(); Vector db = b->VectorGetNum(); if(other) da = da.ScaledBy(-1); Vector ref; DoArcForAngle(a0, da, b0, db, disp.offset, &ref); DoLabel(ref, labelPos, gr, gu); break; } case PERPENDICULAR: { Vector u = Vector::From(0, 0, 0), v = Vector::From(0, 0, 0); Vector rn, ru; if(workplane.v == Entity::FREE_IN_3D.v) { rn = gn; ru = gu; } else { EntityBase *normal = SK.GetEntity(workplane)->Normal(); rn = normal->NormalN(); ru = normal->NormalV(); // ru meaning r_up, not u/v } for(int i = 0; i < 2; i++) { Entity *e = SK.GetEntity(i == 0 ? entityA : entityB); if(i == 0) { // 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); // a bit of bias to stop it from flickering between the // two possibilities if(fabs(u.Dot(ru)) < fabs(v.Dot(ru)) + LENGTH_EPS) { SWAP(Vector, u, v); } if(u.Dot(ru) < 0) u = u.ScaledBy(-1); } Vector p = e->VectorGetRefPoint(); Vector s = p.Plus(u).Plus(v); LineDrawOrGetDistance(s, s.Plus(v)); Vector m = s.Plus(v.ScaledBy(0.5)); LineDrawOrGetDistance(m, m.Plus(u)); } break; } case CURVE_CURVE_TANGENT: case CUBIC_LINE_TANGENT: case ARC_LINE_TANGENT: { Vector textAt, u, v; if(type == ARC_LINE_TANGENT) { Entity *arc = SK.GetEntity(entityA); Entity *norm = SK.GetEntity(arc->normal); Vector c = SK.GetEntity(arc->point[0])->PointGetNum(); Vector p = SK.GetEntity(arc->point[other ? 2 : 1])->PointGetNum(); Vector r = p.Minus(c); textAt = p.Plus(r.WithMagnitude(14/SS.GW.scale)); u = norm->NormalU(); v = norm->NormalV(); } else if(type == CUBIC_LINE_TANGENT) { Vector n; if(workplane.v == Entity::FREE_IN_3D.v) { u = gr; v = gu; n = gn; } else { EntityBase *wn = SK.GetEntity(workplane)->Normal(); u = wn->NormalU(); v = wn->NormalV(); n = wn->NormalN(); } Entity *cubic = SK.GetEntity(entityA); Vector p = other ? cubic->CubicGetFinishNum() : cubic->CubicGetStartNum(); Vector dir = SK.GetEntity(entityB)->VectorGetNum(); Vector out = n.Cross(dir); textAt = p.Plus(out.WithMagnitude(14/SS.GW.scale)); } else { Vector n, dir; EntityBase *wn = SK.GetEntity(workplane)->Normal(); u = wn->NormalU(); v = wn->NormalV(); n = wn->NormalN(); EntityBase *eA = SK.GetEntity(entityA); // Big pain; we have to get a vector tangent to the curve // at the shared point, which could be from either a cubic // or an arc. if(other) { textAt = eA->EndpointFinish(); if(eA->type == Entity::CUBIC) { dir = eA->CubicGetFinishTangentNum(); } else { dir = SK.GetEntity(eA->point[0])->PointGetNum().Minus( SK.GetEntity(eA->point[2])->PointGetNum()); dir = n.Cross(dir); } } else { textAt = eA->EndpointStart(); if(eA->type == Entity::CUBIC) { dir = eA->CubicGetStartTangentNum(); } else { dir = SK.GetEntity(eA->point[0])->PointGetNum().Minus( SK.GetEntity(eA->point[1])->PointGetNum()); dir = n.Cross(dir); } } dir = n.Cross(dir); textAt = textAt.Plus(dir.WithMagnitude(14/SS.GW.scale)); } if(dogd.drawing) { ssglWriteTextRefCenter("T", DEFAULT_TEXT_HEIGHT, textAt, u, v, LineCallback, this); } else { dogd.refp = textAt; Point2d ref = SS.GW.ProjectPoint(dogd.refp); dogd.dmin = min(dogd.dmin, ref.DistanceTo(dogd.mp)-10); } break; } case PARALLEL: { 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); Vector p = e->VectorGetRefPoint(); LineDrawOrGetDistance(p.Plus(u), p.Plus(u).Plus(n)); LineDrawOrGetDistance(p.Minus(u), p.Minus(u).Plus(n)); } break; } case EQUAL_RADIUS: { for(int i = 0; i < 2; i++) { DoEqualRadiusTicks(i == 0 ? entityA : entityB); } break; } case EQUAL_LINE_ARC_LEN: { Entity *line = SK.GetEntity(entityA); DoEqualLenTicks( SK.GetEntity(line->point[0])->PointGetNum(), SK.GetEntity(line->point[1])->PointGetNum(), gn); DoEqualRadiusTicks(entityB); break; } case LENGTH_RATIO: case EQUAL_LENGTH_LINES: { Vector a, b = Vector::From(0, 0, 0); for(int i = 0; i < 2; i++) { Entity *e = SK.GetEntity(i == 0 ? entityA : entityB); a = SK.GetEntity(e->point[0])->PointGetNum(); b = SK.GetEntity(e->point[1])->PointGetNum(); if(workplane.v != Entity::FREE_IN_3D.v) { DoProjectedPoint(&a); DoProjectedPoint(&b); } DoEqualLenTicks(a, b, gn); } if(type == LENGTH_RATIO) { Vector ref = ((a.Plus(b)).ScaledBy(0.5)).Plus(disp.offset); DoLabel(ref, labelPos, gr, gu); } break; } case EQ_LEN_PT_LINE_D: { Entity *forLen = SK.GetEntity(entityA); 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); } DoEqualLenTicks(a, b, gn); 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); la = la.ProjectInto(workplane); lb = lb.ProjectInto(workplane); } Vector closest = pt.ClosestPointOnLine(la, lb.Minus(la)); LineDrawOrGetDistance(pt, closest); DoEqualLenTicks(pt, closest, gn); break; } case EQ_PT_LN_DISTANCES: { for(int i = 0; i < 2; i++) { Entity *ln = SK.GetEntity(i == 0 ? entityA : entityB); Vector la = SK.GetEntity(ln->point[0])->PointGetNum(), lb = SK.GetEntity(ln->point[1])->PointGetNum(); Entity *pte = SK.GetEntity(i == 0 ? ptA : ptB); Vector pt = pte->PointGetNum(); if(workplane.v != Entity::FREE_IN_3D.v) { DoProjectedPoint(&pt); la = la.ProjectInto(workplane); lb = lb.ProjectInto(workplane); } Vector closest = pt.ClosestPointOnLine(la, lb.Minus(la)); LineDrawOrGetDistance(pt, closest); DoEqualLenTicks(pt, closest, gn); } break; } { case SYMMETRIC: Vector n; n = SK.GetEntity(entityA)->Normal()->NormalN(); goto s; case SYMMETRIC_HORIZ: n = SK.GetEntity(workplane)->Normal()->NormalU(); goto s; case SYMMETRIC_VERT: n = SK.GetEntity(workplane)->Normal()->NormalV(); goto s; case SYMMETRIC_LINE: { Entity *ln = SK.GetEntity(entityA); Vector la = SK.GetEntity(ln->point[0])->PointGetNum(), lb = SK.GetEntity(ln->point[1])->PointGetNum(); la = la.ProjectInto(workplane); lb = lb.ProjectInto(workplane); n = lb.Minus(la); Vector nw = SK.GetEntity(workplane)->Normal()->NormalN(); n = n.RotatedAbout(nw, PI/2); goto s; } s: Vector a = SK.GetEntity(ptA)->PointGetNum(); Vector b = SK.GetEntity(ptB)->PointGetNum(); for(int i = 0; i < 2; i++) { Vector tail = (i == 0) ? a : b; Vector d = (i == 0) ? b : a; d = d.Minus(tail); // Project the direction in which the arrow is drawn normal // to the symmetry plane; for projected symmetry constraints, // 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); Vector tip = tail.Plus(d); 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))); } break; } case AT_MIDPOINT: case HORIZONTAL: case VERTICAL: if(entityA.v) { Vector r, u, n; if(workplane.v == Entity::FREE_IN_3D.v) { r = gr; u = gu; n = gn; } else { r = SK.GetEntity(workplane)->Normal()->NormalU(); u = SK.GetEntity(workplane)->Normal()->NormalV(); n = r.Cross(u); } // For "at midpoint", this branch is always taken. Entity *e = SK.GetEntity(entityA); Vector a = SK.GetEntity(e->point[0])->PointGetNum(); 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); // Draw midpoint constraint on other side of line, so that // a line can be midpoint and horizontal at same time. if(type == AT_MIDPOINT) offset = offset.ScaledBy(-1); if(dogd.drawing) { const char *s = (type == HORIZONTAL) ? "H" : ( (type == VERTICAL) ? "V" : ( (type == AT_MIDPOINT) ? "M" : NULL)); ssglWriteTextRefCenter(s, DEFAULT_TEXT_HEIGHT, m.Plus(offset), r, u, LineCallback, this); } else { dogd.refp = m.Plus(offset); Point2d ref = SS.GW.ProjectPoint(dogd.refp); dogd.dmin = min(dogd.dmin, ref.DistanceTo(dogd.mp)-10); } } else { Vector a = SK.GetEntity(ptA)->PointGetNum(); Vector b = SK.GetEntity(ptB)->PointGetNum(); Entity *w = SK.GetEntity(workplane); Vector cu = w->Normal()->NormalU(); Vector cv = w->Normal()->NormalV(); Vector cn = w->Normal()->NormalN(); int i; for(i = 0; i < 2; i++) { Vector o = (i == 0) ? a : b; Vector oo = (i == 0) ? a.Minus(b) : b.Minus(a); Vector d = (type == HORIZONTAL) ? cu : cv; if(oo.Dot(d) < 0) d = d.ScaledBy(-1); Vector dp = cn.Cross(d); d = d.WithMagnitude(14/SS.GW.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 { Point2d ref = SS.GW.ProjectPoint(c); dogd.dmin = min(dogd.dmin, ref.DistanceTo(dogd.mp)-6); } } } break; case 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; v = gu; } else { EntityBase *norm = SK.GetEntity(workplane)->Normal(); u = norm->NormalU(); v = norm->NormalV(); } DoLabel(disp.offset, labelPos, u, v); break; } default: oops(); } }
void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, bool middleDown, bool rightDown, bool shiftDown, bool ctrlDown) { if(GraphicsEditControlIsVisible()) return; if(context.active) return; SS.extraLine.draw = false; if(!orig.mouseDown) { // If someone drags the mouse into our window with the left button // already depressed, then we don't have our starting point; so // don't try. leftDown = false; } if(rightDown) { middleDown = true; shiftDown = !shiftDown; } if(SS.showToolbar) { if(ToolbarMouseMoved((int)x, (int)y)) { hover.Clear(); return; } } if(!leftDown && (pending.operation == DRAGGING_POINTS || pending.operation == DRAGGING_MARQUEE)) { ClearPending(); InvalidateGraphics(); } Point2d mp = Point2d::From(x, y); currentMousePosition = mp; if(rightDown && orig.mouse.DistanceTo(mp) < 5 && !orig.startedMoving) { // Avoid accidentally panning (or rotating if shift is down) if the // user wants a context menu. return; } orig.startedMoving = true; // If the middle button is down, then mouse movement is used to pan and // rotate our view. This wins over everything else. if(middleDown) { hover.Clear(); double dx = (x - orig.mouse.x) / scale; double dy = (y - orig.mouse.y) / scale; if(!(shiftDown || ctrlDown)) { double s = 0.3*(PI/180)*scale; // degrees per pixel projRight = orig.projRight.RotatedAbout(orig.projUp, -s*dx); projUp = orig.projUp.RotatedAbout(orig.projRight, s*dy); NormalizeProjectionVectors(); } else if(ctrlDown) { double theta = atan2(orig.mouse.y, orig.mouse.x); theta -= atan2(y, x); SS.extraLine.draw = true; SS.extraLine.ptA = UnProjectPoint(Point2d::From(0, 0)); SS.extraLine.ptB = UnProjectPoint(mp); Vector normal = orig.projRight.Cross(orig.projUp); projRight = orig.projRight.RotatedAbout(normal, theta); projUp = orig.projUp.RotatedAbout(normal, theta); NormalizeProjectionVectors(); } else { offset.x = orig.offset.x + dx*projRight.x + dy*projUp.x; offset.y = orig.offset.y + dx*projRight.y + dy*projUp.y; offset.z = orig.offset.z + dx*projRight.z + dy*projUp.z; } orig.projRight = projRight; orig.projUp = projUp; orig.offset = offset; orig.mouse.x = x; orig.mouse.y = y; if(SS.TW.shown.screen == TextWindow::SCREEN_EDIT_VIEW) { if(havePainted) { SS.ScheduleShowTW(); } } InvalidateGraphics(); havePainted = false; return; } if(pending.operation == 0) { double dm = orig.mouse.DistanceTo(mp); // If we're currently not doing anything, then see if we should // start dragging something. if(leftDown && dm > 3) { Entity *e = NULL; if(hover.entity.v) e = SK.GetEntity(hover.entity); if(e && e->type != Entity::WORKPLANE) { Entity *e = SK.GetEntity(hover.entity); if(e->type == Entity::CIRCLE && selection.n <= 1) { // Drag the radius. ClearSelection(); pending.circle = hover.entity; pending.operation = DRAGGING_RADIUS; } else if(e->IsNormal()) { ClearSelection(); pending.normal = hover.entity; pending.operation = DRAGGING_NORMAL; } else { if(!hoverWasSelectedOnMousedown) { // The user clicked an unselected entity, which // means they're dragging just the hovered thing, // not the full selection. So clear all the selection // except that entity. ClearSelection(); MakeSelected(e->h); } StartDraggingBySelection(); if(!hoverWasSelectedOnMousedown) { // And then clear the selection again, since they // probably didn't want that selected if they just // were dragging it. ClearSelection(); } hover.Clear(); pending.operation = DRAGGING_POINTS; } } else if(hover.constraint.v && SK.GetConstraint(hover.constraint)->HasLabel()) { ClearSelection(); pending.constraint = hover.constraint; pending.operation = DRAGGING_CONSTRAINT; } if(pending.operation != 0) { // We just started a drag, so remember for the undo before // the drag changes anything. SS.UndoRemember(); } else { if(!hover.constraint.v) { // That's just marquee selection, which should not cause // an undo remember. if(dm > 10) { if(hover.entity.v) { // Avoid accidentally selecting workplanes when // starting drags. MakeUnselected(hover.entity, false); hover.Clear(); } pending.operation = DRAGGING_MARQUEE; orig.marqueePoint = UnProjectPoint(orig.mouseOnButtonDown); } } } } else { // Otherwise, just hit test and give up; but don't hit test // if the mouse is down, because then the user could hover // a point, mouse down (thus selecting it), and drag, in an // effort to drag the point, but instead hover a different // entity before we move far enough to start the drag. if(!leftDown) HitTestMakeSelection(mp); } return; } // If the user has started an operation from the menu, but not // completed it, then just do the selection. if(pending.operation < FIRST_PENDING) { HitTestMakeSelection(mp); return; } // We're currently dragging something; so do that. But if we haven't // painted since the last time we solved, do nothing, because there's // no sense solving a frame and not displaying it. if(!havePainted) { if(pending.operation == DRAGGING_POINTS && ctrlDown) { SS.extraLine.ptA = UnProjectPoint(orig.mouseOnButtonDown); SS.extraLine.ptB = UnProjectPoint(mp); SS.extraLine.draw = true; } return; } switch(pending.operation) { case DRAGGING_CONSTRAINT: { Constraint *c = SK.constraint.FindById(pending.constraint); UpdateDraggedNum(&(c->disp.offset), x, y); orig.mouse = mp; InvalidateGraphics(); break; } case DRAGGING_NEW_LINE_POINT: case DRAGGING_NEW_POINT: UpdateDraggedPoint(pending.point, x, y); HitTestMakeSelection(mp); SS.MarkGroupDirtyByEntity(pending.point); orig.mouse = mp; InvalidateGraphics(); break; case DRAGGING_POINTS: if(shiftDown || ctrlDown) { // Edit the rotation associated with a POINT_N_ROT_TRANS, // either within (ctrlDown) or out of (shiftDown) the plane // of the screen. So first get the rotation to apply, in qt. Quaternion qt; if(ctrlDown) { double d = mp.DistanceTo(orig.mouseOnButtonDown); if(d < 25) { // Don't start dragging the position about the normal // until we're a little ways out, to get a reasonable // reference pos orig.mouse = mp; break; } double theta = atan2(orig.mouse.y-orig.mouseOnButtonDown.y, orig.mouse.x-orig.mouseOnButtonDown.x); theta -= atan2(y-orig.mouseOnButtonDown.y, x-orig.mouseOnButtonDown.x); Vector gn = projRight.Cross(projUp); qt = Quaternion::From(gn, -theta); SS.extraLine.draw = true; SS.extraLine.ptA = UnProjectPoint(orig.mouseOnButtonDown); SS.extraLine.ptB = UnProjectPoint(mp); } else { double dx = -(x - orig.mouse.x); double dy = -(y - orig.mouse.y); double s = 0.3*(PI/180); // degrees per pixel qt = Quaternion::From(projUp, -s*dx).Times( Quaternion::From(projRight, s*dy)); } orig.mouse = mp; // Now apply this rotation to the points being dragged. List<hEntity> *lhe = &(pending.points); for(hEntity *he = lhe->First(); he; he = lhe->NextAfter(he)) { Entity *e = SK.GetEntity(*he); if(e->type != Entity::POINT_N_ROT_TRANS) { if(ctrlDown) { Vector p = e->PointGetNum(); p = p.Minus(SS.extraLine.ptA); p = qt.Rotate(p); p = p.Plus(SS.extraLine.ptA); e->PointForceTo(p); SS.MarkGroupDirtyByEntity(e->h); } continue; } Quaternion q = e->PointGetQuaternion(); Vector p = e->PointGetNum(); q = qt.Times(q); e->PointForceQuaternionTo(q); // Let's rotate about the selected point; so fix up the // translation so that that point didn't move. e->PointForceTo(p); SS.MarkGroupDirtyByEntity(e->h); } } else { List<hEntity> *lhe = &(pending.points); for(hEntity *he = lhe->First(); he; he = lhe->NextAfter(he)) { UpdateDraggedPoint(*he, x, y); SS.MarkGroupDirtyByEntity(*he); } orig.mouse = mp; } break; case DRAGGING_NEW_CUBIC_POINT: { UpdateDraggedPoint(pending.point, x, y); HitTestMakeSelection(mp); hRequest hr = pending.point.request(); if(pending.point.v == hr.entity(4).v) { // The very first segment; dragging final point drags both // tangent points. Vector p0 = SK.GetEntity(hr.entity(1))->PointGetNum(), p3 = SK.GetEntity(hr.entity(4))->PointGetNum(), p1 = p0.ScaledBy(2.0/3).Plus(p3.ScaledBy(1.0/3)), p2 = p0.ScaledBy(1.0/3).Plus(p3.ScaledBy(2.0/3)); SK.GetEntity(hr.entity(1+1))->PointForceTo(p1); SK.GetEntity(hr.entity(1+2))->PointForceTo(p2); } else { // A subsequent segment; dragging point drags only final // tangent point. int i = SK.GetEntity(hr.entity(0))->extraPoints; Vector pn = SK.GetEntity(hr.entity(4+i))->PointGetNum(), pnm2 = SK.GetEntity(hr.entity(2+i))->PointGetNum(), pnm1 = (pn.Plus(pnm2)).ScaledBy(0.5); SK.GetEntity(hr.entity(3+i))->PointForceTo(pnm1); } orig.mouse = mp; SS.MarkGroupDirtyByEntity(pending.point); break; } case DRAGGING_NEW_ARC_POINT: { UpdateDraggedPoint(pending.point, x, y); HitTestMakeSelection(mp); hRequest hr = pending.point.request(); Vector ona = SK.GetEntity(hr.entity(2))->PointGetNum(); Vector onb = SK.GetEntity(hr.entity(3))->PointGetNum(); Vector center = (ona.Plus(onb)).ScaledBy(0.5); SK.GetEntity(hr.entity(1))->PointForceTo(center); orig.mouse = mp; SS.MarkGroupDirtyByEntity(pending.point); break; } case DRAGGING_NEW_RADIUS: case DRAGGING_RADIUS: { Entity *circle = SK.GetEntity(pending.circle); Vector center = SK.GetEntity(circle->point[0])->PointGetNum(); Point2d c2 = ProjectPoint(center); double r = c2.DistanceTo(mp)/scale; SK.GetEntity(circle->distance)->DistanceForceTo(r); SS.MarkGroupDirtyByEntity(pending.circle); break; } case DRAGGING_NORMAL: { Entity *normal = SK.GetEntity(pending.normal); Vector p = SK.GetEntity(normal->point[0])->PointGetNum(); Point2d p2 = ProjectPoint(p); Quaternion q = normal->NormalGetNum(); Vector u = q.RotationU(), v = q.RotationV(); if(ctrlDown) { double theta = atan2(orig.mouse.y-p2.y, orig.mouse.x-p2.x); theta -= atan2(y-p2.y, x-p2.x); Vector normal = projRight.Cross(projUp); u = u.RotatedAbout(normal, -theta); v = v.RotatedAbout(normal, -theta); } else { double dx = -(x - orig.mouse.x); double dy = -(y - orig.mouse.y); double s = 0.3*(PI/180); // degrees per pixel u = u.RotatedAbout(projUp, -s*dx); u = u.RotatedAbout(projRight, s*dy); v = v.RotatedAbout(projUp, -s*dx); v = v.RotatedAbout(projRight, s*dy); } orig.mouse = mp; normal->NormalForceTo(Quaternion::From(u, v)); SS.MarkGroupDirtyByEntity(pending.normal); break; } case DRAGGING_MARQUEE: orig.mouse = mp; InvalidateGraphics(); break; default: oops(); } if(pending.operation != 0 && pending.operation != DRAGGING_CONSTRAINT && pending.operation != DRAGGING_MARQUEE) { SS.GenerateAll(); } havePainted = false; }
void Entity::DrawOrGetDistance(void) { if(!IsVisible()) return; Group *g = SK.GetGroup(group); switch(type) { case POINT_N_COPY: case POINT_N_TRANS: case POINT_N_ROT_TRANS: case POINT_N_ROT_AA: case POINT_IN_3D: case POINT_IN_2D: { Vector v = PointGetNum(); dogd.refp = v; 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; } break; } case NORMAL_N_COPY: case NORMAL_N_ROT: case NORMAL_N_ROT_AA: case NORMAL_IN_3D: case 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) { ssglColorRGB(RGBi(0, 0, f)); } else if(hr.v == Request::HREQUEST_REFERENCE_YZ.v) { ssglColorRGB(RGBi(f, 0, 0)); } else if(hr.v == Request::HREQUEST_REFERENCE_ZX.v) { ssglColorRGB(RGBi(0, f, 0)); } else { ssglColorRGB(Style::Color(Style::NORMALS)); if(i > 0) break; } Quaternion q = NormalGetNum(); Vector tail; if(i == 0) { tail = SK.GetEntity(point[0])->PointGetNum(); glLineWidth(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); ssglDepthRangeLockToFront(true); glLineWidth(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))); } ssglDepthRangeLockToFront(false); break; } case DISTANCE: case DISTANCE_N_COPY: // These are used only as data structures, nothing to display. break; case 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); glLineWidth(1); ssglColorRGB(Style::Color(Style::NORMALS)); glEnable(GL_LINE_STIPPLE); glLineStipple(3, 0x1111); if(!h.isFromRequest()) { mm = mm.Plus(v.ScaledBy(60/SS.GW.scale)); mm2 = mm2.Plus(u.ScaledBy(60/SS.GW.scale)); LineDrawOrGetDistance(mm2, mm); } LineDrawOrGetDistance(pp, pm); LineDrawOrGetDistance(pm, mm2); LineDrawOrGetDistance(mm, mp); LineDrawOrGetDistance(mp, pp); glDisable(GL_LINE_STIPPLE); char *str = DescriptionString()+5; double th = DEFAULT_TEXT_HEIGHT; if(dogd.drawing) { ssglWriteText(str, th, mm2, u, v, NULL, NULL); } else { Vector pos = mm2.Plus(u.ScaledBy(ssglStrWidth(str, th)/2)).Plus( v.ScaledBy(ssglStrHeight(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; } break; } case LINE_SEGMENT: case CIRCLE: case ARC_OF_CIRCLE: case CUBIC: case CUBIC_PERIODIC: case TTF_TEXT: // Nothing but the curve(s). break; case FACE_NORMAL_PT: case FACE_XPROD: case FACE_N_ROT_TRANS: case FACE_N_TRANS: case FACE_N_ROT_AA: // Do nothing; these are drawn with the triangle mesh break; default: oops(); } // And draw the curves; generate the rational polynomial curves for // everything, then piecewise linearize them, and display those. SEdgeList sel; ZERO(&sel); GenerateEdges(&sel, true); int i; for(i = 0; i < sel.l.n; i++) { SEdge *se = &(sel.l.elem[i]); LineDrawOrGetDistance(se->a, se->b, true); } sel.Clear(); }