Snap point to entity when constraining to a hovered entity.

Before this commit, when a point is constrained to an entity (point,
circle, arc of circle or line segment) by clicking on it,
the resulting constraint is not necessarily satisfied, and the next
regeneration may place the newly constrained point somewhere other
than the intended position. After this commit, the parameters
are modified to satisfy the constraint.
pull/200/head
EvilSpirit 2017-02-02 10:48:54 +07:00 committed by whitequark
parent ced42440e7
commit 97e71856b3
4 changed files with 52 additions and 17 deletions

View File

@ -67,8 +67,10 @@ Bugs fixed:
causes the line length to collapse.
* Curve-line constraints (in 3d), parallel constraints (in 3d), and
same orientation constraints are more robust.
* Adding some constraints (vertical, midpoint, etc) twice will now error out
* Adding some constraints (vertical, midpoint, etc) twice errors out
immediately, instead of later and in a confusing way.
* Constraining a newly placed point to a hovered entity does not cause
spurious changes in the sketch.
* Points highlighted with "Analyze → Show Degrees of Freedom" are drawn
on top of all other geometry.

View File

@ -82,8 +82,8 @@ hConstraint Constraint::AddConstraint(Constraint *c) {
hConstraint Constraint::AddConstraint(Constraint *c, bool rememberForUndo) {
if(rememberForUndo) SS.UndoRemember();
SK.constraint.AddAndAssignId(c);
c->Generate(&SK.param);
hConstraint hc = SK.constraint.AddAndAssignId(c);
SK.GetConstraint(hc)->Generate(&SK.param);
SS.MarkGroupDirty(c->group);
SK.GetGroup(c->group)->dofCheckOk = false;

View File

@ -894,22 +894,52 @@ hRequest GraphicsWindow::AddRequest(Request::Type type, bool rememberForUndo) {
return r.h;
}
bool GraphicsWindow::ConstrainPointByHovered(hEntity pt) {
Vector GraphicsWindow::SnapToEntityByScreenPoint(Point2d pp, hEntity he) {
Entity *e = SK.GetEntity(he);
if(e->IsPoint()) return e->PointGetNum();
SEdgeList *edges = e->GetOrGenerateEdges();
double minD = -1.0f;
double k;
const SEdge *edge = NULL;
for(const auto &e : edges->l) {
Point2d p0 = ProjectPoint(e.a);
Point2d p1 = ProjectPoint(e.b);
Point2d dir = p1.Minus(p0);
double d = pp.DistanceToLine(p0, dir, /*asSegment=*/true);
if(minD > 0.0 && d > minD) continue;
minD = d;
k = pp.Minus(p0).Dot(dir) / dir.Dot(dir);
edge = &e;
}
if(edge == NULL) return UnProjectPoint(pp);
return edge->a.Plus(edge->b.Minus(edge->a).ScaledBy(k));
}
bool GraphicsWindow::ConstrainPointByHovered(hEntity pt, const Point2d *projected) {
if(!hover.entity.v) return false;
Entity *point = SK.GetEntity(pt);
Entity *e = SK.GetEntity(hover.entity);
if(e->IsPoint()) {
Entity *point = SK.GetEntity(pt);
point->PointForceTo(e->PointGetNum());
Constraint::ConstrainCoincident(e->h, pt);
return true;
}
if(e->IsCircle()) {
if(projected != NULL) {
Vector snapPos = SnapToEntityByScreenPoint(*projected, e->h);
point->PointForceTo(snapPos);
}
Constraint::Constrain(Constraint::Type::PT_ON_CIRCLE,
pt, Entity::NO_ENTITY, e->h);
return true;
}
if(e->type == Entity::Type::LINE_SEGMENT) {
if(projected != NULL) {
Vector snapPos = SnapToEntityByScreenPoint(*projected, e->h);
point->PointForceTo(snapPos);
}
Constraint::Constrain(Constraint::Type::PT_ON_LINE,
pt, Entity::NO_ENTITY, e->h);
return true;
@ -942,6 +972,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) {
orig.mouse.x = mx;
orig.mouse.y = my;
orig.mouseOnButtonDown = orig.mouse;
Point2d mouse = Point2d::From(mx, my);
// The current mouse location
Vector v = offset.ScaledBy(-1);
@ -956,7 +987,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) {
case Command::DATUM_POINT:
hr = AddRequest(Request::Type::DATUM_POINT);
SK.GetEntity(hr.entity(0))->PointForceTo(v);
ConstrainPointByHovered(hr.entity(0));
ConstrainPointByHovered(hr.entity(0), &mouse);
ClearSuper();
break;
@ -966,7 +997,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) {
hr = AddRequest(Request::Type::LINE_SEGMENT);
SK.GetRequest(hr)->construction = (pending.command == Command::CONSTR_SEGMENT);
SK.GetEntity(hr.entity(1))->PointForceTo(v);
ConstrainPointByHovered(hr.entity(1));
ConstrainPointByHovered(hr.entity(1), &mouse);
ClearSuper();
AddToPending(hr);
@ -1004,7 +1035,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) {
Entity::NO_ENTITY, Entity::NO_ENTITY,
lns[i].entity(0));
}
if(ConstrainPointByHovered(lns[2].entity(1))) {
if(ConstrainPointByHovered(lns[2].entity(1), &mouse)) {
Vector pos = SK.GetEntity(lns[2].entity(1))->PointGetNum();
for(i = 0; i < 4; i++) {
SK.GetEntity(lns[i].entity(1))->PointForceTo(pos);
@ -1028,7 +1059,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) {
// Initial radius zero
SK.GetEntity(hr.entity(64))->DistanceForceTo(0);
ConstrainPointByHovered(hr.entity(1));
ConstrainPointByHovered(hr.entity(1), &mouse);
ClearSuper();
@ -1051,7 +1082,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) {
SK.GetEntity(hr.entity(1))->PointForceTo(v.Minus(adj));
SK.GetEntity(hr.entity(2))->PointForceTo(v);
SK.GetEntity(hr.entity(3))->PointForceTo(v);
ConstrainPointByHovered(hr.entity(2));
ConstrainPointByHovered(hr.entity(2), &mouse);
ClearSuper();
AddToPending(hr);
@ -1067,7 +1098,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) {
SK.GetEntity(hr.entity(2))->PointForceTo(v);
SK.GetEntity(hr.entity(3))->PointForceTo(v);
SK.GetEntity(hr.entity(4))->PointForceTo(v);
ConstrainPointByHovered(hr.entity(1));
ConstrainPointByHovered(hr.entity(1), &mouse);
ClearSuper();
AddToPending(hr);
@ -1088,7 +1119,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) {
SK.GetEntity(hr.entity(1))->PointForceTo(v);
SK.GetEntity(hr.entity(32))->NormalForceTo(
Quaternion::From(SS.GW.projRight, SS.GW.projUp));
ConstrainPointByHovered(hr.entity(1));
ConstrainPointByHovered(hr.entity(1), &mouse);
ClearSuper();
break;
@ -1136,7 +1167,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) {
case Pending::DRAGGING_NEW_POINT:
case Pending::DRAGGING_NEW_ARC_POINT:
ConstrainPointByHovered(pending.point);
ConstrainPointByHovered(pending.point, &mouse);
ClearPending();
break;
@ -1165,7 +1196,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) {
break;
}
if(ConstrainPointByHovered(pending.point)) {
if(ConstrainPointByHovered(pending.point, &mouse)) {
ClearPending();
break;
}
@ -1213,7 +1244,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) {
}
}
if(ConstrainPointByHovered(pending.point)) {
if(ConstrainPointByHovered(pending.point, &mouse)) {
ClearPending();
break;
}
@ -1222,7 +1253,6 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) {
hRequest hr = AddRequest(Request::Type::LINE_SEGMENT);
ReplacePending(pending.request, hr);
SK.GetRequest(hr)->construction = SK.GetRequest(pending.request)->construction;
SK.GetEntity(hr.entity(1))->PointForceTo(v);
// Displace the second point of the new line segment slightly,
// to avoid creating zero-length edge warnings.
SK.GetEntity(hr.entity(2))->PointForceTo(
@ -1230,6 +1260,8 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) {
// Constrain the line segments to share an endpoint
Constraint::ConstrainCoincident(pending.point, hr.entity(1));
Vector pendingPos = SK.GetEntity(pending.point)->PointGetNum();
SK.GetEntity(hr.entity(1))->PointForceTo(pendingPos);
// And drag an endpoint of the new line segment
pending.operation = Pending::DRAGGING_NEW_LINE_POINT;

View File

@ -728,7 +728,8 @@ public:
bool SuggestLineConstraint(hRequest lineSegment, ConstraintBase::Type *type);
Vector SnapToGrid(Vector p);
bool ConstrainPointByHovered(hEntity pt);
Vector SnapToEntityByScreenPoint(Point2d pp, hEntity he);
bool ConstrainPointByHovered(hEntity pt, const Point2d *projected = NULL);
void DeleteTaggedRequests();
hRequest AddRequest(Request::Type type, bool rememberForUndo);
hRequest AddRequest(Request::Type type);