From b480613763c98e71002298611d6acd5fe0f1c3f8 Mon Sep 17 00:00:00 2001 From: Jonathan Westhues Date: Sat, 17 May 2008 03:15:14 -0800 Subject: [PATCH] Add angle constraints. I'm doing these differently from SketchFlat, as a constraint on the direction cosine, rather than driving the dot product against a rotated vector to zero. The drawing is the ugly part; to do that for skew lines, I gave up. Also add a function to clear non-existent items on the selection after solving, since that could have caused an oops(). [git-p4: depot-paths = "//depot/solvespace/": change = 1727] --- constraint.cpp | 108 ++++++++++++++++++++++++++++++++++++++------- drawconstraint.cpp | 78 ++++++++++++++++++++++++++++++++ dsc.h | 1 + file.cpp | 1 + graphicswin.cpp | 51 +++++++++++++++------ sketch.cpp | 4 +- sketch.h | 4 +- solvespace.cpp | 4 ++ textwin.cpp | 1 + ui.h | 7 +++ util.cpp | 15 +++++-- 11 files changed, 237 insertions(+), 37 deletions(-) diff --git a/constraint.cpp b/constraint.cpp index db59e678..0d81c23c 100644 --- a/constraint.cpp +++ b/constraint.cpp @@ -237,6 +237,33 @@ void Constraint::MenuConstrain(int id) { AddConstraint(&c); break; + case GraphicsWindow::MNU_OTHER_ANGLE: + if(gs.constraints == 1 && gs.n == 0) { + Constraint *c = SS.GetConstraint(gs.constraint[0]); + if(c->type == ANGLE) { + c->otherAngle = !(c->otherAngle); + c->ModifyToSatisfy(); + break; + } + } + Error("Must select an angle constraint."); + break; + + case GraphicsWindow::MNU_ANGLE: + if(gs.vectors == 2 && gs.n == 2) { + c.type = ANGLE; + c.entityA = gs.vector[0]; + c.entityB = gs.vector[1]; + c.exprA = Expr::FromConstant(0)->DeepCopyKeep(); + c.otherAngle = true; + } else { + Error("Bad selection for angle constraint."); + return; + } + c.ModifyToSatisfy(); + AddConstraint(&c); + break; + case GraphicsWindow::MNU_PARALLEL: if(gs.vectors == 2 && gs.n == 2) { c.type = PARALLEL; @@ -374,21 +401,37 @@ ExprVector Constraint::PointInThreeSpace(hEntity workplane, Expr *u, Expr *v) { } void Constraint::ModifyToSatisfy(void) { - IdList l; - // An uninit IdList could lead us to free some random address, bad. - memset(&l, 0, sizeof(l)); + if(type == ANGLE) { + Vector a = SS.GetEntity(entityA)->VectorGetNum(); + Vector b = SS.GetEntity(entityB)->VectorGetNum(); + if(otherAngle) a = a.ScaledBy(-1); + if(workplane.v != Entity::FREE_IN_3D.v) { + a = a.ProjectVectorInto(workplane); + b = b.ProjectVectorInto(workplane); + } + double c = (a.Dot(b))/(a.Magnitude() * b.Magnitude()); + double theta = acos(c)*180/PI; + Expr::FreeKeep(&exprA); + exprA = Expr::FromConstant(theta)->DeepCopyKeep(); + } else { + // We'll fix these ones up by looking at their symbolic equation; + // that means no extra work. + IdList l; + // An uninit IdList could lead us to free some random address, bad. + memset(&l, 0, sizeof(l)); - Generate(&l); - if(l.n != 1) oops(); + Generate(&l); + if(l.n != 1) oops(); - // These equations are written in the form f(...) - d = 0, where - // d is the value of the exprA. - double v = (l.elem[0].e)->Eval(); - double nd = exprA->Eval() + v; - Expr::FreeKeep(&exprA); - exprA = Expr::FromConstant(nd)->DeepCopyKeep(); + // These equations are written in the form f(...) - d = 0, where + // d is the value of the exprA. + double v = (l.elem[0].e)->Eval(); + double nd = exprA->Eval() + v; + Expr::FreeKeep(&exprA); + exprA = Expr::FromConstant(nd)->DeepCopyKeep(); - l.Clear(); + l.Clear(); + } } void Constraint::AddEq(IdList *l, Expr *expr, int index) { @@ -399,19 +442,22 @@ void Constraint::AddEq(IdList *l, Expr *expr, int index) { } void Constraint::Generate(IdList *l) { + Expr *exA = NULL; + if(exprA) exA = exprA->DeepCopy(); + switch(type) { case PT_PT_DISTANCE: - AddEq(l, Distance(workplane, ptA, ptB)->Minus(exprA), 0); + AddEq(l, Distance(workplane, ptA, ptB)->Minus(exA), 0); break; case PT_LINE_DISTANCE: AddEq(l, - PointLineDistance(workplane, ptA, entityA)->Minus(exprA), 0); + PointLineDistance(workplane, ptA, entityA)->Minus(exA), 0); break; case PT_PLANE_DISTANCE: { ExprVector pt = SS.GetEntity(ptA)->PointGetExprs(); - AddEq(l, (PointPlaneDistance(pt, entityA))->Minus(exprA), 0); + AddEq(l, (PointPlaneDistance(pt, entityA))->Minus(exA), 0); break; } @@ -428,14 +474,14 @@ void Constraint::Generate(IdList *l) { Entity *b = SS.GetEntity(entityB); Expr *la = Distance(workplane, a->point[0], a->point[1]); Expr *lb = Distance(workplane, b->point[0], b->point[1]); - AddEq(l, (la->Div(lb))->Minus(exprA), 0); + AddEq(l, (la->Div(lb))->Minus(exA), 0); break; } case DIAMETER: { Entity *circle = SS.GetEntity(entityA); Expr *r = circle->CircleGetRadiusExpr(); - AddEq(l, (r->Times(Expr::FromConstant(2)))->Minus(exprA), 0); + AddEq(l, (r->Times(Expr::FromConstant(2)))->Minus(exA), 0); break; } @@ -656,6 +702,34 @@ void Constraint::Generate(IdList *l) { break; } + case ANGLE: { + Entity *a = SS.GetEntity(entityA); + Entity *b = SS.GetEntity(entityB); + ExprVector ae = a->VectorGetExprs(); + ExprVector be = b->VectorGetExprs(); + if(otherAngle) ae = ae.ScaledBy(Expr::FromConstant(-1)); + Expr *c; + if(workplane.v == Entity::FREE_IN_3D.v) { + Expr *mags = (ae.Magnitude())->Times(be.Magnitude()); + c = (ae.Dot(be))->Div(mags); + } else { + Entity *w = SS.GetEntity(workplane); + ExprVector u = w->Normal()->NormalExprsU(); + ExprVector v = w->Normal()->NormalExprsV(); + Expr *ua = u.Dot(ae); + Expr *va = v.Dot(ae); + Expr *ub = u.Dot(be); + Expr *vb = v.Dot(be); + Expr *maga = (ua->Square()->Plus(va->Square()))->Sqrt(); + Expr *magb = (ub->Square()->Plus(vb->Square()))->Sqrt(); + Expr *dot = (ua->Times(ub))->Plus(va->Times(vb)); + c = dot->Div(maga->Times(magb)); + } + Expr *rads = exA->Times(Expr::FromConstant(PI/180)); + AddEq(l, c->Minus(rads->Cos()), 0); + break; + } + case PARALLEL: { ExprVector a = SS.GetEntity(entityA)->VectorGetExprs(); ExprVector b = SS.GetEntity(entityB)->VectorGetExprs(); diff --git a/drawconstraint.cpp b/drawconstraint.cpp index 84c3ae7a..25646f9d 100644 --- a/drawconstraint.cpp +++ b/drawconstraint.cpp @@ -7,6 +7,7 @@ bool Constraint::HasLabel(void) { case PT_PT_DISTANCE: case DIAMETER: case LENGTH_RATIO: + case ANGLE: return true; default: @@ -247,6 +248,83 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) { break; } + case ANGLE: { + Entity *a = SS.GetEntity(entityA); + Entity *b = SS.GetEntity(entityB); + + Vector a0 = a->VectorGetRefPoint(); + Vector b0 = b->VectorGetRefPoint(); + Vector da = a->VectorGetNum(); + Vector db = b->VectorGetNum(); + if(otherAngle) da = da.ScaledBy(-1); + + if(workplane.v != Entity::FREE_IN_3D.v) { + a0 = a0.ProjectInto(workplane); + b0 = b0.ProjectInto(workplane); + da = da.ProjectVectorInto(workplane); + db = db.ProjectVectorInto(workplane); + } + + // Make an orthogonal coordinate system from those directions + Vector dn = da.Cross(db); // normal to both + Vector dna = dn.Cross(da); // normal to da + Vector dnb = dn.Cross(db); // normal to db + // At the intersection of the lines + // a0 + pa*da = b0 + pb*db (where pa, pb are scalar params) + // So dot this equation against dna and dnb to get two equations + // to solve for da and db + double pb = ((a0.Minus(b0)).Dot(dna))/(db.Dot(dna)); + double pa = -((a0.Minus(b0)).Dot(dnb))/(da.Dot(dnb)); + + Vector pi = a0.Plus(da.ScaledBy(pa)); + Vector ref; + if(pi.Equals(b0.Plus(db.ScaledBy(pb)))) { + ref = pi.Plus(disp.offset); + // We draw in a coordinate system centered at pi, with + // basis vectors da and dna. + da = da.WithMagnitude(1); dna = dna.WithMagnitude(1); + Vector rm = ref.Minus(pi); + double rda = rm.Dot(da), rdna = rm.Dot(dna); + double r = sqrt(rda*rda + rdna*rdna); + double c = (da.Dot(db))/(da.Magnitude()*db.Magnitude()); + double thetaf = acos(c); + + Vector m = da.ScaledBy(cos(thetaf/2)).Plus( + dna.ScaledBy(sin(thetaf/2))); + if(m.Dot(rm) < 0) { + da = da.ScaledBy(-1); dna = dna.ScaledBy(-1); + } + + Vector prev = da.ScaledBy(r).Plus(pi); + int i, n = 30; + for(i = 0; i <= n; i++) { + double theta = (i*thetaf)/n; + Vector p = da. ScaledBy(r*cos(theta)).Plus( + dna.ScaledBy(r*sin(theta))).Plus(pi); + LineDrawOrGetDistance(prev, p); + prev = p; + } + + double tl = atan2(rm.Dot(gu), rm.Dot(gr)); + double adj = EllipticalInterpolation( + glxStrWidth(exprA->Print())/2, glxStrHeight()/2, tl); + ref = ref.Plus(rm.WithMagnitude(adj + 3/SS.GW.scale)); + } else { + // The lines are skew; no wonderful way to illustrate that. + ref = a->VectorGetRefPoint().Plus(b->VectorGetRefPoint()); + ref = ref.ScaledBy(0.5).Plus(disp.offset); + glPushMatrix(); + gu = gu.WithMagnitude(1); + glxTranslatev(ref.Plus(gu.ScaledBy(-1.5*glxStrHeight()))); + glxOntoWorkplane(gr, gu); + glxWriteTextRefCenter("angle between skew lines"); + glPopMatrix(); + } + + DoLabel(ref, labelPos, gr, gu); + break; + } + case PARALLEL: { for(int i = 0; i < 2; i++) { Entity *e = SS.GetEntity(i == 0 ? entityA : entityB); diff --git a/dsc.h b/dsc.h index 61f47e86..ec6aba31 100644 --- a/dsc.h +++ b/dsc.h @@ -53,6 +53,7 @@ public: Vector WithMagnitude(double s); Vector ScaledBy(double s); Vector ProjectInto(hEntity wrkpl); + Vector ProjectVectorInto(hEntity wrkpl); double DivPivoting(Vector delta); Vector ClosestOrtho(void); Point2d Project2d(Vector u, Vector v); diff --git a/file.cpp b/file.cpp index 76cfb119..e4b5c4df 100644 --- a/file.cpp +++ b/file.cpp @@ -112,6 +112,7 @@ const SolveSpace::SaveTable SolveSpace::SAVED[] = { { 'c', "Constraint.ptC.v", 'x', &(SS.sv.c.ptC.v) }, { 'c', "Constraint.entityA.v", 'x', &(SS.sv.c.entityA.v) }, { 'c', "Constraint.entityB.v", 'x', &(SS.sv.c.entityB.v) }, + { 'c', "Constraint.otherAngle", 'b', &(SS.sv.c.otherAngle) }, { 'c', "Constraint.disp.offset.x", 'f', &(SS.sv.c.disp.offset.x) }, { 'c', "Constraint.disp.offset.y", 'f', &(SS.sv.c.disp.offset.y) }, { 'c', "Constraint.disp.offset.z", 'f', &(SS.sv.c.disp.offset.z) }, diff --git a/graphicswin.cpp b/graphicswin.cpp index ac450721..f17fc629 100644 --- a/graphicswin.cpp +++ b/graphicswin.cpp @@ -70,8 +70,8 @@ const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = { { 0, "&Constrain", 0, NULL }, { 1, "&Distance / Diameter\tShift+D", MNU_DISTANCE_DIA, 'D'|S, mCon }, -{ 1, "A&ngle\tShift+N", 0, 'N'|S, NULL }, -{ 1, "Other S&upplementary Angle\tShift+U", 0, 'U'|S, NULL }, +{ 1, "A&ngle\tShift+N", MNU_ANGLE, 'N'|S, mCon }, +{ 1, "Other S&upplementary Angle\tShift+U", MNU_OTHER_ANGLE, 'U'|S, mCon }, { 1, NULL, 0, NULL }, { 1, "&Horizontal\tShift+H", MNU_HORIZONTAL, 'H'|S, mCon }, { 1, "&Vertical\tShift+V", MNU_VERTICAL, 'V'|S, mCon }, @@ -302,8 +302,7 @@ void GraphicsWindow::MenuEdit(int id) { SS.constraint.RemoveTagged(); // Forget any mention of the just-deleted entity - SS.GW.ClearSelection(); - SS.GW.hover.Clear(); + SS.GW.ClearSuper(); // And regenerate to get rid of what it generates, plus anything // that references it (since the regen code checks for that). SS.GW.GeneratePerSolving(); @@ -633,6 +632,13 @@ void GraphicsWindow::Selection::Draw(void) { if(constraint.v) SS.GetConstraint(constraint)->Draw(); } +void GraphicsWindow::ClearSuper(void) { + ClearPending(); + ClearSelection(); + hover.Clear(); + EnsureValidActives(); +} + void GraphicsWindow::ClearPending(void) { memset(&pending, 0, sizeof(pending)); } @@ -680,6 +686,22 @@ void GraphicsWindow::ClearSelection(void) { InvalidateGraphics(); } +void GraphicsWindow::ClearNonexistentSelectionItems(void) { + bool change = false; + for(int i = 0; i < MAX_SELECTED; i++) { + Selection *s = &(selection[i]); + if(s->constraint.v && !(SS.constraint.FindByIdNoOops(s->constraint))) { + s->constraint.v = 0; + change = true; + } + if(s->entity.v && !(SS.entity.FindByIdNoOops(s->entity))) { + s->entity.v = 0; + change = true; + } + } + if(change) InvalidateGraphics(); +} + void GraphicsWindow::GroupSelection(void) { memset(&gs, 0, sizeof(gs)); int i; @@ -718,6 +740,9 @@ void GraphicsWindow::GroupSelection(void) { case Entity::CIRCLE: (gs.circlesOrArcs)++; break; } } + if(s->constraint.v) { + gs.constraint[(gs.constraints)++] = s->constraint; + } } } @@ -794,7 +819,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { hr = AddRequest(Request::DATUM_POINT); SS.GetEntity(hr.entity(0))->PointForceTo(v); - ClearSelection(); hover.Clear(); + ClearSuper(); pending.operation = 0; break; @@ -804,7 +829,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { SS.GetEntity(hr.entity(1))->PointForceTo(v); ConstrainPointByHovered(hr.entity(1)); - ClearSelection(); hover.Clear(); + ClearSuper(); pending.operation = DRAGGING_NEW_LINE_POINT; pending.point = hr.entity(2); @@ -848,9 +873,8 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { Quaternion::MakeFrom(SS.GW.projRight, SS.GW.projUp)); ConstrainPointByHovered(hr.entity(1)); - ClearSelection(); hover.Clear(); + ClearSuper(); - ClearPending(); pending.operation = DRAGGING_NEW_RADIUS; pending.circle = hr.entity(0); pending.description = "click to set radius"; @@ -869,9 +893,8 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { SS.GetEntity(hr.entity(3))->PointForceTo(v); ConstrainPointByHovered(hr.entity(2)); - ClearSelection(); hover.Clear(); + ClearSuper(); - ClearPending(); pending.operation = DRAGGING_NEW_ARC_POINT; pending.point = hr.entity(3); pending.description = "click to place point"; @@ -885,7 +908,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { SS.GetEntity(hr.entity(4))->PointForceTo(v); ConstrainPointByHovered(hr.entity(1)); - ClearSelection(); hover.Clear(); + ClearSuper(); pending.operation = DRAGGING_NEW_CUBIC_POINT; pending.point = hr.entity(4); @@ -899,8 +922,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { Quaternion::MakeFrom(SS.GW.projRight, SS.GW.projUp)); ConstrainPointByHovered(hr.entity(1)); - ClearSelection(); hover.Clear(); - ClearPending(); + ClearSuper(); break; case DRAGGING_RADIUS: @@ -986,12 +1008,13 @@ void GraphicsWindow::MouseLeftDoubleClick(double mx, double my) { if(GraphicsEditControlIsVisible()) return; if(hover.constraint.v) { - ClearSelection(); Constraint *c = SS.GetConstraint(hover.constraint); Vector p3 = c->GetLabelPos(); Point2d p2 = ProjectPoint(p3); ShowGraphicsEditControl((int)p2.x, (int)p2.y, c->exprA->Print()); constraintBeingEdited = hover.constraint; + + ClearSuper(); } } diff --git a/sketch.cpp b/sketch.cpp index 519d902b..23fec1e3 100644 --- a/sketch.cpp +++ b/sketch.cpp @@ -497,7 +497,9 @@ void Group::Draw(void) { int i; glEnable(GL_LIGHTING); GLfloat vec[] = { 0.3f, 0.3f, 0.3f, 1.0 }; - glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, vec); + glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, vec); + GLfloat vec2[] = { 1.0f, 0.3f, 0.3f, 1.0 }; + glMaterialfv(GL_BACK, GL_AMBIENT_AND_DIFFUSE, vec2); for(i = 0; i < faces.n; i++) { glxFillPolygon(&(faces.elem[i])); #if 0 diff --git a/sketch.h b/sketch.h index 04be53f2..54d3786b 100644 --- a/sketch.h +++ b/sketch.h @@ -343,7 +343,8 @@ public: static const int DIAMETER = 90; static const int PT_ON_CIRCLE = 100; static const int SAME_ORIENTATION = 110; - static const int PARALLEL = 120; + static const int ANGLE = 120; + static const int PARALLEL = 121; static const int EQUAL_RADIUS = 130; int tag; @@ -362,6 +363,7 @@ public: hEntity ptC; hEntity entityA; hEntity entityB; + bool otherAngle; // These define how the constraint is drawn on-screen. struct { diff --git a/solvespace.cpp b/solvespace.cpp index 9f435afb..91dde18a 100644 --- a/solvespace.cpp +++ b/solvespace.cpp @@ -184,6 +184,10 @@ void SolveSpace::GenerateAll(bool andSolve) { prev.Clear(); InvalidateGraphics(); + // Remove nonexistent selection items, for same reason we waited till + // the end to put up a dialog box. + GW.ClearNonexistentSelectionItems(); + if(deleted.requests > 0 || deleted.constraints > 0 || deleted.groups > 0) { // Don't display any errors until we've regenerated fully. The // sketch is not necessarily in a consistent state until we've diff --git a/textwin.cpp b/textwin.cpp index b53c08d1..fbf15b4f 100644 --- a/textwin.cpp +++ b/textwin.cpp @@ -344,6 +344,7 @@ void TextWindow::ScreenChangeExtrudeSides(int link, DWORD v) { g->subtype = Group::EXTRUDE_ONE_SIDED; } SS.GW.GeneratePerSolving(); + SS.GW.ClearSuper(); } void TextWindow::ShowGroupInfo(void) { Group *g = SS.group.FindById(shown->group); diff --git a/ui.h b/ui.h index 32fc1678..79fd719d 100644 --- a/ui.h +++ b/ui.h @@ -120,6 +120,8 @@ public: MNU_GROUP_TRANS, // Constrain MNU_DISTANCE_DIA, + MNU_ANGLE, + MNU_OTHER_ANGLE, MNU_EQUAL, MNU_RATIO, MNU_ON_ENTITY, @@ -227,11 +229,13 @@ public: Selection selection[MAX_SELECTED]; void HitTestMakeSelection(Point2d mp); void ClearSelection(void); + void ClearNonexistentSelectionItems(void); struct { hEntity point[MAX_SELECTED]; hEntity entity[MAX_SELECTED]; hEntity anyNormal[MAX_SELECTED]; hEntity vector[MAX_SELECTED]; + hConstraint constraint[MAX_SELECTED]; int points; int entities; int workplanes; @@ -239,10 +243,13 @@ public: int circlesOrArcs; int anyNormals; int vectors; + int constraints; int n; } gs; void GroupSelection(void); + void ClearSuper(void); + // This sets what gets displayed. bool showWorkplanes; bool showNormals; diff --git a/util.cpp b/util.cpp index 0a9fb8c8..b6fa9a98 100644 --- a/util.cpp +++ b/util.cpp @@ -307,17 +307,24 @@ Vector Vector::WithMagnitude(double v) { } } -Vector Vector::ProjectInto(hEntity wrkpl) { +Vector Vector::ProjectVectorInto(hEntity wrkpl) { Entity *w = SS.GetEntity(wrkpl); Vector u = w->Normal()->NormalU(); Vector v = w->Normal()->NormalV(); + + double up = this->Dot(u); + double vp = this->Dot(v); + + return (u.ScaledBy(up)).Plus(v.ScaledBy(vp)); +} + +Vector Vector::ProjectInto(hEntity wrkpl) { + Entity *w = SS.GetEntity(wrkpl); Vector p0 = w->WorkplaneGetOffset(); Vector f = this->Minus(p0); - double up = f.Dot(u); - double vp = f.Dot(v); - return p0.Plus((u.ScaledBy(up)).Plus(v.ScaledBy(vp))); + return p0.Plus(f.ProjectVectorInto(wrkpl)); } Point2d Vector::Project2d(Vector u, Vector v) {