diff --git a/constraint.cpp b/constraint.cpp index be60735..4907cee 100644 --- a/constraint.cpp +++ b/constraint.cpp @@ -13,26 +13,21 @@ hConstraint Constraint::AddConstraint(Constraint *c) { return c->h; } -void Constraint::ConstrainCoincident(hEntity ptA, hEntity ptB) { +void Constraint::Constrain(int type, hEntity ptA, hEntity ptB, hEntity entityA) +{ Constraint c; memset(&c, 0, sizeof(c)); c.group = SS.GW.activeGroup; c.workplane = SS.GW.activeWorkplane; - c.type = POINTS_COINCIDENT; + c.type = type; c.ptA = ptA; c.ptB = ptB; + c.entityA = entityA; AddConstraint(&c); } -void Constraint::ConstrainHorizVert(bool horiz, hEntity ls) { - Constraint c; - memset(&c, 0, sizeof(c)); - c.group = SS.GW.activeGroup; - c.workplane = SS.GW.activeWorkplane; - if(c.workplane.v == Entity::FREE_IN_3D.v) oops(); - c.type = (horiz ? HORIZONTAL : VERTICAL); - c.entityA = ls; - AddConstraint(&c); +void Constraint::ConstrainCoincident(hEntity ptA, hEntity ptB) { + Constrain(POINTS_COINCIDENT, ptA, ptB, Entity::NO_ENTITY); } void Constraint::MenuConstrain(int id) { @@ -55,6 +50,14 @@ void Constraint::MenuConstrain(int id) { Entity *e = SS.GetEntity(gs.entity[0]); c.ptA = e->point[0]; c.ptB = e->point[1]; + } else if(gs.workplanes == 1 && gs.points == 1 && gs.n == 2) { + c.type = PT_PLANE_DISTANCE; + c.ptA = gs.point[0]; + c.entityA = gs.entity[0]; + } else if(gs.lineSegments == 1 && gs.points == 1 && gs.n == 2) { + c.type = PT_LINE_DISTANCE; + c.ptA = gs.point[0]; + c.entityA = gs.entity[0]; } else if(gs.circlesOrArcs == 1 && gs.n == 1) { c.type = DIAMETER; c.entityA = gs.entity[0]; @@ -90,6 +93,10 @@ void Constraint::MenuConstrain(int id) { c.type = PT_ON_LINE; c.ptA = gs.point[0]; c.entityA = gs.entity[0]; + } else if(gs.points == 1 && gs.circlesOrArcs == 1 && gs.n == 2) { + c.type = PT_ON_CIRCLE; + c.ptA = gs.point[0]; + c.entityA = gs.entity[0]; } else { Error("Bad selection for on point / curve / plane constraint."); return; @@ -329,6 +336,17 @@ void Constraint::Generate(IdList *l) { AddEq(l, Distance(workplane, ptA, ptB)->Minus(exprA), 0); break; + case PT_LINE_DISTANCE: + AddEq(l, + PointLineDistance(workplane, ptA, entityA)->Minus(exprA), 0); + break; + + case PT_PLANE_DISTANCE: { + ExprVector pt = SS.GetEntity(ptA)->PointGetExprs(); + AddEq(l, (PointPlaneDistance(pt, entityA))->Minus(exprA), 0); + break; + } + case EQUAL_LENGTH_LINES: { Entity *a = SS.GetEntity(entityA); Entity *b = SS.GetEntity(entityB); @@ -390,6 +408,14 @@ void Constraint::Generate(IdList *l) { } break; + case PT_ON_CIRCLE: { + Entity *circle = SS.GetEntity(entityA); + hEntity center = circle->point[0]; + Expr *radius = SS.GetEntity(circle->distance)->DistanceGetExpr(); + AddEq(l, Distance(workplane, ptA, center)->Minus(radius), 0); + break; + } + case AT_MIDPOINT: if(workplane.v == Entity::FREE_IN_3D.v) { Entity *ln = SS.GetEntity(entityA); diff --git a/drawconstraint.cpp b/drawconstraint.cpp index a105d8d..0b9686a 100644 --- a/drawconstraint.cpp +++ b/drawconstraint.cpp @@ -2,6 +2,8 @@ bool Constraint::HasLabel(void) { switch(type) { + case PT_LINE_DISTANCE: + case PT_PLANE_DISTANCE: case PT_PT_DISTANCE: case DIAMETER: return true; @@ -55,6 +57,15 @@ void Constraint::DoLabel(Vector ref, Vector *labelPos, Vector gr, Vector gu) { } } +void Constraint::DoProjectedPoint(Vector *r) { + Vector p = r->ProjectInto(workplane); + glLineStipple(4, 0x5555); + glEnable(GL_LINE_STIPPLE); + LineDrawOrGetDistance(p, *r); + glDisable(GL_LINE_STIPPLE); + *r = p; +} + void Constraint::DrawOrGetDistance(Vector *labelPos) { if(!SS.GW.showConstraints) return; Group *g = SS.GetGroup(group); @@ -76,6 +87,11 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) { Vector ap = SS.GetEntity(ptA)->PointGetNum(); Vector bp = SS.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); Vector ab = ap.Minus(bp); @@ -92,6 +108,52 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) { break; } + case PT_PLANE_DISTANCE: { + Vector pt = SS.GetEntity(ptA)->PointGetNum(); + Entity *plane = SS.GetEntity(entityA); + Vector n = plane->Normal()->NormalN(); + Vector p = plane->WorkplaneGetOffset(); + double d = (p.Minus(pt)).Dot(n); + + Vector closest = pt.Plus(n.WithMagnitude(d)); + LineDrawOrGetDistance(pt, closest); + + Vector ref = ((closest.Plus(pt)).ScaledBy(0.5)).Plus(disp.offset); + DoLabel(ref, labelPos, gr, gu); + break; + } + + case PT_LINE_DISTANCE: { + Vector pt = SS.GetEntity(ptA)->PointGetNum(); + Entity *line = SS.GetEntity(entityA); + Vector lA = SS.GetEntity(line->point[0])->PointGetNum(); + Vector lB = SS.GetEntity(line->point[1])->PointGetNum(); + + if(workplane.v != Entity::FREE_IN_3D.v) { + lA = lA.ProjectInto(workplane); + lB = lB.ProjectInto(workplane); + DoProjectedPoint(&pt); + } + + Vector lAB = (lA.Minus(lB)).WithMagnitude(1); + Vector closest; + // lA, lB, and pt define a plane; the min distance is in + // that plane, so calculate its normal + Vector pn = (pt.Minus(lA)).Cross(lAB); + // The minimum distance line is in that plane, perpendicular + // to the line + Vector n = pn.Cross(lAB); + + // Calculate the actual distance + double d = (lAB.Cross(lA.Minus(pt))).Magnitude(); + closest = pt.Plus(n.WithMagnitude(d)); + + LineDrawOrGetDistance(pt, closest); + Vector ref = ((closest.Plus(pt)).ScaledBy(0.5)).Plus(disp.offset); + DoLabel(ref, labelPos, gr, gu); + break; + } + case DIAMETER: { Entity *circle = SS.GetEntity(entityA); Vector center = SS.GetEntity(circle->point[0])->PointGetNum(); @@ -142,6 +204,7 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) { break; } + case PT_ON_CIRCLE: case PT_ON_LINE: case PT_IN_PLANE: { double s = 7/SS.GW.scale; diff --git a/dsc.h b/dsc.h index 2c2c6fd..1da3023 100644 --- a/dsc.h +++ b/dsc.h @@ -6,6 +6,8 @@ typedef unsigned long DWORD; typedef unsigned char BYTE; class Vector; +class Point2d; +class hEntity; class Quaternion { public: @@ -45,6 +47,7 @@ public: double Magnitude(void); Vector WithMagnitude(double s); Vector ScaledBy(double s); + Vector ProjectInto(hEntity wrkpl); }; class Point2d { diff --git a/entity.cpp b/entity.cpp index 042a53c..bcb36b4 100644 --- a/entity.cpp +++ b/entity.cpp @@ -5,6 +5,10 @@ char *Entity::DescriptionString(void) { return r->DescriptionString(); } +bool Entity::IsCircle(void) { + return (type == CIRCLE); +} + bool Entity::IsWorkplane(void) { return (type == WORKPLANE); } @@ -526,3 +530,23 @@ void Entity::DrawOrGetDistance(int order) { } } +void Entity::AddEq(IdList *l, Expr *expr, int index) { + Equation eq; + eq.e = expr; + eq.h = h.equation(index); + l->Add(&eq); +} + +void Entity::GenerateEquations(IdList *l) { + switch(type) { + case NORMAL_IN_3D: { + ExprQuaternion q = NormalGetExprs(); + AddEq(l, (q.Magnitude())->Minus(Expr::FromConstant(1)), 0); + break; + } + default:; + // Most entities do not generate equations. + } +} + + diff --git a/expr.cpp b/expr.cpp index e80f6c0..fd9b284 100644 --- a/expr.cpp +++ b/expr.cpp @@ -117,6 +117,13 @@ ExprVector ExprQuaternion::RotationN(void) { return n; } +Expr *ExprQuaternion::Magnitude(void) { + return ((w ->Square())->Plus( + (vx->Square())->Plus( + (vy->Square())->Plus( + (vz->Square())))))->Sqrt(); +} + Expr *Expr::FromParam(hParam p) { Expr *r = AllocExpr(); r->op = PARAM; diff --git a/expr.h b/expr.h index 059c817..b84e260 100644 --- a/expr.h +++ b/expr.h @@ -141,6 +141,8 @@ public: ExprVector RotationU(void); ExprVector RotationV(void); ExprVector RotationN(void); + + Expr *Magnitude(void); }; #endif diff --git a/file.cpp b/file.cpp index 25c38c8..c8a6e73 100644 --- a/file.cpp +++ b/file.cpp @@ -86,6 +86,7 @@ const SolveSpace::SaveTable SolveSpace::SAVED[] = { { 'e', "Entity.numNormal.vx", 'f', &(SS.sv.e.numNormal.vx) }, { 'e', "Entity.numNormal.vy", 'f', &(SS.sv.e.numNormal.vy) }, { 'e', "Entity.numNormal.vz", 'f', &(SS.sv.e.numNormal.vz) }, + { 'e', "Entity.numDistance", 'f', &(SS.sv.e.numDistance) }, { 'c', "Constraint.h.v", 'x', &(SS.sv.c.h.v) }, { 'c', "Constraint.type", 'd', &(SS.sv.c.type) }, diff --git a/graphicswin.cpp b/graphicswin.cpp index 7ab934a..cdb66ba 100644 --- a/graphicswin.cpp +++ b/graphicswin.cpp @@ -658,11 +658,43 @@ hRequest GraphicsWindow::AddRequest(int type) { r.workplane = activeWorkplane; r.type = type; SS.request.AddAndAssignId(&r); - SS.GenerateAll(solving == SOLVE_ALWAYS); + + // We must regenerate the parameters, so that the code that tries to + // place this request's entities where the mouse is can do so. But + // we mustn't try to solve until reasonable values have been supplied + // for these new parameters, or else we'll get a numerical blowup. + SS.GenerateAll(false); return r.h; } +bool GraphicsWindow::ConstrainPointByHovered(hEntity pt) { + if(!hover.entity.v) return false; + + Entity *e = SS.GetEntity(hover.entity); + if(e->IsPoint()) { + Constraint::ConstrainCoincident(e->h, pt); + return true; + } + if(e->IsWorkplane()) { + Constraint::Constrain(Constraint::PT_IN_PLANE, + pt, Entity::NO_ENTITY, e->h); + return true; + } + if(e->IsCircle()) { + Constraint::Constrain(Constraint::PT_ON_CIRCLE, + pt, Entity::NO_ENTITY, e->h); + return true; + } + if(e->type == Entity::LINE_SEGMENT) { + Constraint::Constrain(Constraint::PT_ON_LINE, + pt, Entity::NO_ENTITY, e->h); + return true; + } + + return false; +} + void GraphicsWindow::MouseLeftDown(double mx, double my) { if(GraphicsEditControlIsVisible()) return; @@ -676,10 +708,6 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { v = v.Plus(projRight.ScaledBy(mx/scale)); v = v.Plus(projUp.ScaledBy(my/scale)); -#define MAYBE_PLACE(p) \ - if(hover.entity.v && SS.GetEntity((p))->IsPoint()) { \ - Constraint::ConstrainCoincident(hover.entity, (p)); \ - } hRequest hr; switch(pending.operation) { case MNU_DATUM_POINT: @@ -694,7 +722,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { case MNU_LINE_SEGMENT: hr = AddRequest(Request::LINE_SEGMENT); SS.GetEntity(hr.entity(1))->PointForceTo(v); - MAYBE_PLACE(hr.entity(1)); + ConstrainPointByHovered(hr.entity(1)); ClearSelection(); hover.Clear(); @@ -717,7 +745,10 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { SS.GetEntity(lns[i].entity(2))->PointForceTo(v); } for(i = 0; i < 4; i++) { - Constraint::ConstrainHorizVert((i % 2)==0, lns[i].entity(0)); + Constraint::Constrain( + (i % 2) ? Constraint::HORIZONTAL : Constraint::VERTICAL, + Entity::NO_ENTITY, Entity::NO_ENTITY, + lns[i].entity(0)); } pending.operation = DRAGGING_NEW_POINT; @@ -730,7 +761,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { SS.GetEntity(hr.entity(1))->PointForceTo(v); SS.GetEntity(hr.entity(32))->NormalForceTo( Quaternion::MakeFrom(SS.GW.projRight, SS.GW.projUp)); - MAYBE_PLACE(hr.entity(1)); + ConstrainPointByHovered(hr.entity(1)); ClearSelection(); hover.Clear(); @@ -747,7 +778,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { SS.GetEntity(hr.entity(2))->PointForceTo(v); SS.GetEntity(hr.entity(3))->PointForceTo(v); SS.GetEntity(hr.entity(4))->PointForceTo(v); - MAYBE_PLACE(hr.entity(1)); + ConstrainPointByHovered(hr.entity(1)); ClearSelection(); hover.Clear(); @@ -761,7 +792,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { SS.GetEntity(hr.entity(1))->PointForceTo(v); SS.GetEntity(hr.entity(32))->NormalForceTo( Quaternion::MakeFrom(SS.GW.projRight, SS.GW.projUp)); - MAYBE_PLACE(hr.entity(1)); + ConstrainPointByHovered(hr.entity(1)); ClearSelection(); hover.Clear(); ClearPending(); @@ -774,15 +805,12 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { break; case DRAGGING_NEW_CUBIC_POINT: - if(hover.entity.v && SS.GetEntity(hover.entity)->IsPoint()) { - Constraint::ConstrainCoincident(pending.point, hover.entity); - } + ConstrainPointByHovered(pending.point); ClearPending(); break; case DRAGGING_NEW_LINE_POINT: { - if(hover.entity.v && SS.GetEntity(hover.entity)->IsPoint()) { - Constraint::ConstrainCoincident(pending.point, hover.entity); + if(ConstrainPointByHovered(pending.point)) { ClearPending(); break; } diff --git a/sketch.cpp b/sketch.cpp index fd84fdc..d2ddbcb 100644 --- a/sketch.cpp +++ b/sketch.cpp @@ -1,6 +1,7 @@ #include "solvespace.h" const hEntity Entity::FREE_IN_3D = { 0 }; +const hEntity Entity::NO_ENTITY = { 0 }; const hGroup Group::HGROUP_REFERENCES = { 1 }; const hRequest Request::HREQUEST_REFERENCE_XY = { 1 }; diff --git a/sketch.h b/sketch.h index d799b27..5acc9d8 100644 --- a/sketch.h +++ b/sketch.h @@ -41,6 +41,7 @@ public: inline bool isFromRequest(void); inline hRequest request(void); + inline hEquation equation(int i); }; class hParam { public: @@ -153,6 +154,7 @@ public: hEntity h; static const hEntity FREE_IN_3D; + static const hEntity NO_ENTITY; static const int POINT_IN_3D = 2000; static const int POINT_IN_2D = 2001; @@ -201,6 +203,8 @@ public: // times to apply the transformation. int timesApplied; + bool IsCircle(void); + bool HasDirection(void); ExprVector GetDirection(void); @@ -253,6 +257,9 @@ public: double GetDistance(Point2d mp); void GenerateEdges(SEdgeList *el); + void AddEq(IdList *l, Expr *expr, int index); + void GenerateEquations(IdList *l); + char *DescriptionString(void); }; @@ -270,31 +277,6 @@ public: }; -inline hEntity hGroup::entity(int i) - { hEntity r; r.v = 0x80000000 | (v << 16) | i; return r; } -inline hParam hGroup::param(int i) - { hParam r; r.v = 0x80000000 | (v << 16) | i; return r; } - -inline bool hRequest::IsFromReferences(void) { - if(v == Request::HREQUEST_REFERENCE_XY.v) return true; - if(v == Request::HREQUEST_REFERENCE_YZ.v) return true; - if(v == Request::HREQUEST_REFERENCE_ZX.v) return true; - return false; -} -inline hEntity hRequest::entity(int i) - { hEntity r; r.v = (v << 16) | i; return r; } -inline hParam hRequest::param(int i) - { hParam r; r.v = (v << 16) | i; return r; } - -inline bool hEntity::isFromRequest(void) - { if(v & 0x80000000) return false; else return true; } -inline hRequest hEntity::request(void) - { hRequest r; r.v = (v >> 16); return r; } - -inline hRequest hParam::request(void) - { hRequest r; r.v = (v >> 16); return r; } - - class hConstraint { public: DWORD v; @@ -304,18 +286,20 @@ public: class Constraint { public: - static const int USER_EQUATION = 10; - static const int POINTS_COINCIDENT = 20; - static const int PT_PT_DISTANCE = 30; - static const int PT_LINE_DISTANCE = 31; - static const int PT_IN_PLANE = 40; - static const int PT_ON_LINE = 41; - static const int EQUAL_LENGTH_LINES = 50; - static const int SYMMETRIC = 60; - static const int AT_MIDPOINT = 70; - static const int HORIZONTAL = 80; - static const int VERTICAL = 81; - static const int DIAMETER = 90; + static const int USER_EQUATION = 10; + static const int POINTS_COINCIDENT = 20; + static const int PT_PT_DISTANCE = 30; + static const int PT_LINE_DISTANCE = 31; + static const int PT_PLANE_DISTANCE = 32; + static const int PT_IN_PLANE = 40; + static const int PT_ON_LINE = 41; + static const int EQUAL_LENGTH_LINES = 50; + static const int SYMMETRIC = 60; + static const int AT_MIDPOINT = 70; + static const int HORIZONTAL = 80; + static const int VERTICAL = 81; + static const int DIAMETER = 90; + static const int PT_ON_CIRCLE = 100; int tag; hConstraint h; @@ -353,6 +337,7 @@ public: void DrawOrGetDistance(Vector *labelPos); double EllipticalInterpolation(double rx, double ry, double theta); void DoLabel(Vector ref, Vector *labelPos, Vector gr, Vector gu); + void DoProjectedPoint(Vector *p); double GetDistance(Point2d mp); Vector GetLabelPos(void); @@ -371,7 +356,7 @@ public: static ExprVector PointInThreeSpace(hEntity workplane, Expr *u, Expr *v); static void ConstrainCoincident(hEntity ptA, hEntity ptB); - static void ConstrainHorizVert(bool horiz, hEntity lineSegment); + static void Constrain(int type, hEntity ptA, hEntity ptB, hEntity entityA); }; class hEquation { @@ -387,6 +372,34 @@ public: Expr *e; }; + +inline hEntity hGroup::entity(int i) + { hEntity r; r.v = 0x80000000 | (v << 16) | i; return r; } +inline hParam hGroup::param(int i) + { hParam r; r.v = 0x80000000 | (v << 16) | i; return r; } + +inline bool hRequest::IsFromReferences(void) { + if(v == Request::HREQUEST_REFERENCE_XY.v) return true; + if(v == Request::HREQUEST_REFERENCE_YZ.v) return true; + if(v == Request::HREQUEST_REFERENCE_ZX.v) return true; + return false; +} +inline hEntity hRequest::entity(int i) + { hEntity r; r.v = (v << 16) | i; return r; } +inline hParam hRequest::param(int i) + { hParam r; r.v = (v << 16) | i; return r; } + +inline bool hEntity::isFromRequest(void) + { if(v & 0x80000000) return false; else return true; } +inline hRequest hEntity::request(void) + { hRequest r; r.v = (v >> 16); return r; } +inline hEquation hEntity::equation(int i) + { if(i != 0) oops(); hEquation r; r.v = v | 0x80000000; return r; } + +inline hRequest hParam::request(void) + { hRequest r; r.v = (v >> 16); return r; } + + inline hEquation hConstraint::equation(int i) { hEquation r; r.v = (v << 16) | i; return r; } diff --git a/solvespace.cpp b/solvespace.cpp index 926b9aa..fc51190 100644 --- a/solvespace.cpp +++ b/solvespace.cpp @@ -127,6 +127,13 @@ bool SolveSpace::SolveGroup(hGroup hg) { c->Generate(&(sys.eq)); } + // And the equations from entities + for(i = 0; i < entity.n; i++) { + Entity *e = &(entity.elem[i]); + if(e->group.v != hg.v) continue; + + e->GenerateEquations(&(sys.eq)); + } bool r = sys.Solve(); FreeAllTemporary(); diff --git a/system.cpp b/system.cpp index 317be18..e6fa9ea 100644 --- a/system.cpp +++ b/system.cpp @@ -70,6 +70,17 @@ bool System::IsDragged(hParam p) { } } } + if(SS.GW.pending.circle.v) { + Entity *circ = SS.entity.FindByIdNoOops(SS.GW.pending.circle); + if(circ) { + Entity *dist = SS.GetEntity(circ->distance); + switch(dist->type) { + case Entity::DISTANCE: + if(p.v == (dist->param[0].v)) return true; + break; + } + } + } return false; } @@ -358,7 +369,10 @@ bool System::Solve(void) { for(i = 0; i < eq.n; i++) { dbp(" %.3f = %s = 0", eq.elem[i].e->Eval(), eq.elem[i].e->Print()); } - dbp("%d parameters", param.n); */ + dbp("%d parameters", param.n); + for(i = 0; i < param.n; i++) { + dbp(" param %08x at %.3f", param.elem[i].h.v, param.elem[i].val); + } */ param.ClearTags(); eq.ClearTags(); diff --git a/ui.h b/ui.h index 26be832..b0ce66c 100644 --- a/ui.h +++ b/ui.h @@ -196,6 +196,7 @@ public: // The constraint that is being edited with the on-screen textbox. hConstraint constraintBeingEdited; + bool ConstrainPointByHovered(hEntity pt); hRequest AddRequest(int type); // The current selection. diff --git a/util.cpp b/util.cpp index bfe2987..abb6fd9 100644 --- a/util.cpp +++ b/util.cpp @@ -262,6 +262,19 @@ Vector Vector::WithMagnitude(double v) { } } +Vector Vector::ProjectInto(hEntity wrkpl) { + Entity *w = SS.GetEntity(wrkpl); + Vector u = w->Normal()->NormalU(); + Vector v = w->Normal()->NormalV(); + 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))); +} + Point2d Point2d::Plus(Point2d b) { Point2d r; r.x = x + b.x; @@ -322,3 +335,4 @@ double Point2d::DistanceToLine(Point2d p0, Point2d dp, bool segment) { } } +