From 853c6cb59c091c4a040a8aa62b6c46cb9b8c2cdc Mon Sep 17 00:00:00 2001 From: Jonathan Westhues Date: Sun, 4 May 2008 22:18:01 -0800 Subject: [PATCH] A big change, to add a concept of normals. These are "oriented vectors", represented by unit quaternions. This permits me to add circles, where the normal defines the plane of the circle. Still many things painful. The interface for editing normals is not so intuitive, and it's not yet clear how I would e.g. export a circle entity and recreate it properly, since that entity has a param not associated with a normal or point. And the transformed points/normals do not yet support rotations. That will be necessary soon. [git-p4: depot-paths = "//depot/solvespace/": change = 1705] --- constraint.cpp | 4 +- drawconstraint.cpp | 28 +++-- dsc.h | 11 +- entity.cpp | 220 ++++++++++++++++++++++++++---------- expr.cpp | 44 ++++++++ expr.h | 11 ++ file.cpp | 9 +- graphicswin.cpp | 276 +++++++++++++++++++++++++++++---------------- sketch.cpp | 160 ++++++++++++++++++-------- sketch.h | 32 ++++-- solvespace.cpp | 27 ++--- solvespace.h | 1 + system.cpp | 4 +- textwin.cpp | 16 +-- ui.h | 30 +++-- util.cpp | 96 ++++++++-------- 16 files changed, 653 insertions(+), 316 deletions(-) diff --git a/constraint.cpp b/constraint.cpp index f89101d..7c666d7 100644 --- a/constraint.cpp +++ b/constraint.cpp @@ -49,8 +49,8 @@ void Constraint::MenuConstrain(int id) { return; } Vector n = SS.GW.projRight.Cross(SS.GW.projUp); - Vector a = SS.GetEntity(c.ptA)->PointGetCoords(); - Vector b = SS.GetEntity(c.ptB)->PointGetCoords(); + Vector a = SS.GetEntity(c.ptA)->PointGetNum(); + Vector b = SS.GetEntity(c.ptB)->PointGetNum(); c.disp.offset = n.Cross(a.Minus(b)).WithMagnitude(50); c.exprA = Expr::FromString("0")->DeepCopyKeep(); diff --git a/drawconstraint.cpp b/drawconstraint.cpp index 84d69a4..14637ee 100644 --- a/drawconstraint.cpp +++ b/drawconstraint.cpp @@ -41,8 +41,8 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) { glxColor3d(1, 0.2, 1); switch(type) { case PT_PT_DISTANCE: { - Vector ap = SS.GetEntity(ptA)->PointGetCoords(); - Vector bp = SS.GetEntity(ptB)->PointGetCoords(); + Vector ap = SS.GetEntity(ptA)->PointGetNum(); + Vector bp = SS.GetEntity(ptB)->PointGetNum(); Vector ref = ((ap.Plus(bp)).ScaledBy(0.5)).Plus(disp.offset); if(labelPos) *labelPos = ref; @@ -74,8 +74,7 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) { case POINTS_COINCIDENT: { if(!dogd.drawing) { for(int i = 0; i < 2; i++) { - Vector p = SS.GetEntity(i == 0 ? ptA : ptB)-> - PointGetCoords(); + Vector p = SS.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 @@ -89,8 +88,7 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) { 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 = SS.GetEntity(i == 0 ? ptA : ptB)-> - PointGetCoords(); + Vector p = SS.GetEntity(i == 0 ? ptA : ptB)-> PointGetNum(); glxColor3d(0.4, 0, 0.4); glBegin(GL_QUADS); glxVertex3v(p.Plus (r).Plus (d)); @@ -107,7 +105,7 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) { case PT_ON_LINE: case PT_IN_PLANE: { double s = 7; - Vector p = SS.GetEntity(ptA)->PointGetCoords(); + Vector p = SS.GetEntity(ptA)->PointGetNum(); Vector r = gr.WithMagnitude(s); Vector d = gu.WithMagnitude(s); LineDrawOrGetDistance(p.Plus (r).Plus (d), p.Plus (r).Minus(d)); @@ -120,8 +118,8 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) { case EQUAL_LENGTH_LINES: { for(int i = 0; i < 2; i++) { Entity *e = SS.GetEntity(i == 0 ? entityA : entityB); - Vector a = SS.GetEntity(e->point[0])->PointGetCoords(); - Vector b = SS.GetEntity(e->point[1])->PointGetCoords(); + Vector a = SS.GetEntity(e->point[0])->PointGetNum(); + Vector b = SS.GetEntity(e->point[1])->PointGetNum(); Vector m = (a.ScaledBy(1.0/3)).Plus(b.ScaledBy(2.0/3)); Vector ab = a.Minus(b); Vector n = (gn.Cross(ab)).WithMagnitude(10/SS.GW.scale); @@ -132,8 +130,8 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) { } case SYMMETRIC: { - Vector a = SS.GetEntity(ptA)->PointGetCoords(); - Vector b = SS.GetEntity(ptB)->PointGetCoords(); + Vector a = SS.GetEntity(ptA)->PointGetNum(); + Vector b = SS.GetEntity(ptB)->PointGetNum(); Vector n = SS.GetEntity(entityA)->WorkplaneGetNormalVector(); for(int i = 0; i < 2; i++) { Vector tail = (i == 0) ? a : b; @@ -168,8 +166,8 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) { } // For "at midpoint", this branch is always taken. Entity *e = SS.GetEntity(entityA); - Vector a = SS.GetEntity(e->point[0])->PointGetCoords(); - Vector b = SS.GetEntity(e->point[1])->PointGetCoords(); + Vector a = SS.GetEntity(e->point[0])->PointGetNum(); + Vector b = SS.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); @@ -191,8 +189,8 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) { dogd.dmin = min(dogd.dmin, ref.DistanceTo(dogd.mp)-10); } } else { - Vector a = SS.GetEntity(ptA)->PointGetCoords(); - Vector b = SS.GetEntity(ptB)->PointGetCoords(); + Vector a = SS.GetEntity(ptA)->PointGetNum(); + Vector b = SS.GetEntity(ptB)->PointGetNum(); Entity *w = SS.GetEntity(SS.GetEntity(ptA)->workplane); Vector cu, cv, cn; diff --git a/dsc.h b/dsc.h index f558ba0..234cf01 100644 --- a/dsc.h +++ b/dsc.h @@ -9,14 +9,14 @@ class Vector; class Quaternion { public: - // a + bi + cj + dk - double a, b, c, d; + // a + (vx)*i + (vy)*j + (vz)*k + double w, vx, vy, vz; - static Quaternion MakeFrom(double a, double b, double c, double d); + static Quaternion MakeFrom(double w, double vx, double vy, double vz); static Quaternion MakeFrom(Vector u, Vector v); - Quaternion Plus(Quaternion y); - Quaternion Minus(Quaternion y); + Quaternion Plus(Quaternion b); + Quaternion Minus(Quaternion b); Quaternion ScaledBy(double s); double Magnitude(void); Quaternion WithMagnitude(double s); @@ -25,6 +25,7 @@ public: // second rows, where that matrix is generated by this quaternion Vector RotationU(void); Vector RotationV(void); + Vector RotationN(void); }; class Vector { diff --git a/entity.cpp b/entity.cpp index 920029c..b55ea84 100644 --- a/entity.cpp +++ b/entity.cpp @@ -6,11 +6,7 @@ char *Entity::DescriptionString(void) { } void Entity::WorkplaneGetBasisVectors(Vector *u, Vector *v) { - double q[4]; - for(int i = 0; i < 4; i++) { - q[i] = SS.GetParam(param[i])->val; - } - Quaternion quat = Quaternion::MakeFrom(q[0], q[1], q[2], q[3]); + Quaternion quat = SS.GetEntity(normal)->NormalGetNum(); *u = quat.RotationU(); *v = quat.RotationV(); @@ -23,34 +19,10 @@ Vector Entity::WorkplaneGetNormalVector(void) { } void Entity::WorkplaneGetBasisExprs(ExprVector *u, ExprVector *v) { - Expr *a = Expr::FromParam(param[0]); - Expr *b = Expr::FromParam(param[1]); - Expr *c = Expr::FromParam(param[2]); - Expr *d = Expr::FromParam(param[3]); + ExprQuaternion q = SS.GetEntity(normal)->NormalGetExprs(); - Expr *two = Expr::FromConstant(2); - - u->x = a->Square(); - u->x = (u->x)->Plus(b->Square()); - u->x = (u->x)->Minus(c->Square()); - u->x = (u->x)->Minus(d->Square()); - - u->y = two->Times(a->Times(d)); - u->y = (u->y)->Plus(two->Times(b->Times(c))); - - u->z = two->Times(b->Times(d)); - u->z = (u->z)->Minus(two->Times(a->Times(c))); - - v->x = two->Times(b->Times(c)); - v->x = (v->x)->Minus(two->Times(a->Times(d))); - - v->y = a->Square(); - v->y = (v->y)->Minus(b->Square()); - v->y = (v->y)->Plus(c->Square()); - v->y = (v->y)->Minus(d->Square()); - - v->z = two->Times(a->Times(b)); - v->z = (v->z)->Plus(two->Times(c->Times(d))); + *u = q.RotationU(); + *v = q.RotationV(); } ExprVector Entity::WorkplaneGetOffsetExprs(void) { @@ -98,22 +70,94 @@ void Entity::PlaneGetExprs(ExprVector *n, Expr **dn) { bool Entity::IsPoint(void) { switch(type) { case POINT_IN_3D: - // A point by (x, y, z) in our base coordinate system. These - // variables are given by param[0:2]. case POINT_IN_2D: - // A point by (u, v) in a workplane. These variables are given - // by param[0:1], and the workplane is given in workplane. - case POINT_XFRMD: - // A point by a translation of another point. The original - // point is given by point[0], and the three offsets in - // param[0:2]. - return true; + case POINT_XFRMD: return true; - default: - return false; + default: return false; } } +bool Entity::IsNormal(void) { + switch(type) { + case NORMAL_IN_3D: + case NORMAL_IN_2D: + case NORMAL_XFRMD: return true; + + default: return false; + } +} + +Quaternion Entity::NormalGetNum(void) { + Quaternion q; + switch(type) { + case NORMAL_IN_3D: + q.w = SS.GetParam(param[0])->val; + q.vx = SS.GetParam(param[1])->val; + q.vy = SS.GetParam(param[2])->val; + q.vz = SS.GetParam(param[3])->val; + break; + + case NORMAL_IN_2D: { + Entity *wrkpl = SS.GetEntity(workplane); + Entity *norm = SS.GetEntity(wrkpl->normal); + q = norm->NormalGetNum(); + break; + } + case NORMAL_XFRMD: + q = numNormal; + break; + + default: oops(); + } + return q; +} + +void Entity::NormalForceTo(Quaternion q) { + switch(type) { + case NORMAL_IN_3D: + SS.GetParam(param[0])->val = q.w; + SS.GetParam(param[1])->val = q.vx; + SS.GetParam(param[2])->val = q.vy; + SS.GetParam(param[3])->val = q.vz; + break; + + case NORMAL_IN_2D: + case NORMAL_XFRMD: + // There's absolutely nothing to do; these are locked. + break; + + default: oops(); + } +} + +ExprQuaternion Entity::NormalGetExprs(void) { + ExprQuaternion q; + switch(type) { + case NORMAL_IN_3D: + q.w = Expr::FromParam(param[0]); + q.vx = Expr::FromParam(param[1]); + q.vy = Expr::FromParam(param[2]); + q.vz = Expr::FromParam(param[3]); + break; + + case NORMAL_IN_2D: { + Entity *wrkpl = SS.GetEntity(workplane); + Entity *norm = SS.GetEntity(wrkpl->normal); + q = norm->NormalGetExprs(); + break; + } + case NORMAL_XFRMD: + q.w = Expr::FromConstant(numNormal.w); + q.vx = Expr::FromConstant(numNormal.vx); + q.vy = Expr::FromConstant(numNormal.vy); + q.vz = Expr::FromConstant(numNormal.vz); + break; + + default: oops(); + } + return q; +} + bool Entity::PointIsFromReferences(void) { return h.request().IsFromReferences(); } @@ -136,8 +180,7 @@ void Entity::PointForceTo(Vector p) { } case POINT_XFRMD: { - Vector orig = SS.GetEntity(point[0])->PointGetCoords(); - Vector trans = p.Minus(orig); + Vector trans = p.Minus(numPoint); SS.GetParam(param[0])->val = trans.x; SS.GetParam(param[1])->val = trans.y; SS.GetParam(param[2])->val = trans.z; @@ -148,7 +191,7 @@ void Entity::PointForceTo(Vector p) { } } -Vector Entity::PointGetCoords(void) { +Vector Entity::PointGetNum(void) { Vector p; switch(type) { case POINT_IN_3D: @@ -167,7 +210,7 @@ Vector Entity::PointGetCoords(void) { } case POINT_XFRMD: { - p = SS.GetEntity(point[0])->PointGetCoords(); + p = numPoint; p.x += SS.GetParam(param[0])->val; p.y += SS.GetParam(param[1])->val; p.z += SS.GetParam(param[2])->val; @@ -197,7 +240,10 @@ ExprVector Entity::PointGetExprs(void) { break; } case POINT_XFRMD: { - ExprVector orig = SS.GetEntity(point[0])->PointGetExprs(); + ExprVector orig = { + Expr::FromConstant(numPoint.x), + Expr::FromConstant(numPoint.y), + Expr::FromConstant(numPoint.z) }; ExprVector trans; trans.x = Expr::FromParam(param[0]); trans.y = Expr::FromParam(param[1]); @@ -307,13 +353,17 @@ void Entity::DrawOrGetDistance(int order) { } } - Vector v = PointGetCoords(); + Vector v = PointGetNum(); if(dogd.drawing) { double s = 3; Vector r = SS.GW.projRight.ScaledBy(s/SS.GW.scale); Vector d = SS.GW.projUp.ScaledBy(s/SS.GW.scale); + // The usual fudge, to make this appear in front. + Vector gn = SS.GW.projRight.Cross(SS.GW.projUp); + v = v.Plus(gn.ScaledBy(4/SS.GW.scale)); + glxColor3d(0, 0.8, 0); glBegin(GL_QUADS); glxVertex3v(v.Plus (r).Plus (d)); @@ -330,12 +380,42 @@ void Entity::DrawOrGetDistance(int order) { break; } + case NORMAL_IN_3D: + case NORMAL_IN_2D: + case NORMAL_XFRMD: { + if(order >= 0 && order != 2) break; + if(!SS.GW.showNormals) break; + + hRequest hr = h.request(); + double f = 0.5; + if(hr.v == Request::HREQUEST_REFERENCE_XY.v) { + glxColor3d(0, 0, f); + } else if(hr.v == Request::HREQUEST_REFERENCE_YZ.v) { + glxColor3d(f, 0, 0); + } else if(hr.v == Request::HREQUEST_REFERENCE_ZX.v) { + glxColor3d(0, f, 0); + } else { + glxColor3d(0, 0.4, 0.4); + } + Quaternion q = NormalGetNum(); + Vector tail = SS.GetEntity(point[0])->PointGetNum(); + Vector v = (q.RotationN()).WithMagnitude(50/SS.GW.scale); + Vector tip = tail.Plus(v); + LineDrawOrGetDistance(tail, tip); + + v = v.WithMagnitude(12); + Vector axis = q.RotationV(); + LineDrawOrGetDistance(tip, tip.Minus(v.RotatedAbout(axis, 0.6))); + LineDrawOrGetDistance(tip, tip.Minus(v.RotatedAbout(axis, -0.6))); + break; + } + case WORKPLANE: { if(order >= 0 && order != 0) break; if(!SS.GW.showWorkplanes) break; Vector p; - p = SS.GetEntity(point[0])->PointGetCoords(); + p = SS.GetEntity(point[0])->PointGetNum(); Vector u, v; WorkplaneGetBasisVectors(&u, &v); @@ -350,7 +430,7 @@ void Entity::DrawOrGetDistance(int order) { Vector mm = p.Minus(us).Minus(vs); Vector mp = p.Minus(us).Plus (vs); - glxColor3d(0, 0.4, 0.4); + glxColor3d(0, 0.3, 0.3); LineDrawOrGetDistance(pp, pm); LineDrawOrGetDistance(pm, mm); LineDrawOrGetDistance(mm, mp); @@ -362,23 +442,28 @@ void Entity::DrawOrGetDistance(int order) { glxOntoWorkplane(u, v); glxWriteText(DescriptionString()); glPopMatrix(); + } else { + // If a line lies in a plane, then select the line, not + // the plane. + dogd.dmin += 3; } break; } case LINE_SEGMENT: { if(order >= 0 && order != 1) break; - Vector a = SS.GetEntity(point[0])->PointGetCoords(); - Vector b = SS.GetEntity(point[1])->PointGetCoords(); + Vector a = SS.GetEntity(point[0])->PointGetNum(); + Vector b = SS.GetEntity(point[1])->PointGetNum(); LineDrawOrGetDistanceOrEdge(a, b); break; } case CUBIC: { - Vector p0 = SS.GetEntity(point[0])->PointGetCoords(); - Vector p1 = SS.GetEntity(point[1])->PointGetCoords(); - Vector p2 = SS.GetEntity(point[2])->PointGetCoords(); - Vector p3 = SS.GetEntity(point[3])->PointGetCoords(); + if(order >= 0 && order != 1) break; + Vector p0 = SS.GetEntity(point[0])->PointGetNum(); + Vector p1 = SS.GetEntity(point[1])->PointGetNum(); + Vector p2 = SS.GetEntity(point[2])->PointGetNum(); + Vector p3 = SS.GetEntity(point[3])->PointGetNum(); int i, n = 20; Vector prev = p0; for(i = 1; i <= n; i++) { @@ -394,6 +479,27 @@ void Entity::DrawOrGetDistance(int order) { break; } + case CIRCLE: { + if(order >= 0 && order != 1) break; + + Quaternion q = SS.GetEntity(normal)->NormalGetNum(); + double r = SS.GetParam(param[0])->val; + Vector center = SS.GetEntity(point[0])->PointGetNum(); + Vector u = q.RotationU(), v = q.RotationV(); + + int i, c = 20; + Vector prev = u.ScaledBy(r).Plus(center); + for(i = 0; i <= c; i++) { + double phi = (2*PI*i)/c; + Vector p = (u.ScaledBy(r*cos(phi))).Plus( + v.ScaledBy(r*sin(phi))); + p = p.Plus(center); + LineDrawOrGetDistanceOrEdge(prev, p); + prev = p; + } + break; + } + default: oops(); } diff --git a/expr.cpp b/expr.cpp index 118569d..2691c8a 100644 --- a/expr.cpp +++ b/expr.cpp @@ -53,7 +53,51 @@ Expr *ExprVector::Magnitude(void) { return r->Sqrt(); } +ExprQuaternion ExprQuaternion::FromExprs(Expr *w, Expr *vx, Expr *vy, Expr *vz) +{ + ExprQuaternion q; + q.w = w; + q.vx = vx; + q.vy = vy; + q.vz = vz; + return q; +} +ExprVector ExprQuaternion::RotationU(void) { + ExprVector u; + Expr *two = Expr::FromConstant(2); + + u.x = w->Square(); + u.x = (u.x)->Plus(vx->Square()); + u.x = (u.x)->Minus(vy->Square()); + u.x = (u.x)->Minus(vz->Square()); + + u.y = two->Times(w->Times(vz)); + u.y = (u.y)->Plus(two->Times(vx->Times(vy))); + + u.z = two->Times(vx->Times(vz)); + u.z = (u.z)->Minus(two->Times(w->Times(vy))); + + return u; +} + +ExprVector ExprQuaternion::RotationV(void) { + ExprVector v; + Expr *two = Expr::FromConstant(2); + + v.x = two->Times(vx->Times(vy)); + v.x = (v.x)->Minus(two->Times(w->Times(vz))); + + v.y = w->Square(); + v.y = (v.y)->Minus(vx->Square()); + v.y = (v.y)->Plus(vy->Square()); + v.y = (v.y)->Minus(vz->Square()); + + v.z = two->Times(w->Times(vx)); + v.z = (v.z)->Plus(two->Times(vy->Times(vz))); + + return v; +} Expr *Expr::FromParam(hParam p) { Expr *r = AllocExpr(); diff --git a/expr.h b/expr.h index 1153f48..44adf2c 100644 --- a/expr.h +++ b/expr.h @@ -131,4 +131,15 @@ public: Expr *Magnitude(void); }; +class ExprQuaternion { +public: + Expr *w, *vx, *vy, *vz; + + static ExprQuaternion FromExprs(Expr *w, Expr *vx, Expr *vy, Expr *vz); + + ExprVector RotationU(void); + ExprVector RotationV(void); +}; + #endif + diff --git a/file.cpp b/file.cpp index c6a783b..cda4037 100644 --- a/file.cpp +++ b/file.cpp @@ -75,8 +75,15 @@ const SolveSpace::SaveTable SolveSpace::SAVED[] = { { 'e', "Entity.point[1].v", 'x', &(SS.sv.e.point[1].v) }, { 'e', "Entity.point[2].v", 'x', &(SS.sv.e.point[2].v) }, { 'e', "Entity.point[3].v", 'x', &(SS.sv.e.point[3].v) }, - { 'e', "Entity.direction.v", 'x', &(SS.sv.e.direction.v) }, + { 'e', "Entity.normal.v", 'x', &(SS.sv.e.normal.v) }, { 'e', "Entity.workplane.v", 'x', &(SS.sv.e.workplane.v) }, + { 'e', "Entity.numPoint.x", 'f', &(SS.sv.e.numPoint.x) }, + { 'e', "Entity.numPoint.y", 'f', &(SS.sv.e.numPoint.y) }, + { 'e', "Entity.numPoint.z", 'f', &(SS.sv.e.numPoint.z) }, + { 'e', "Entity.numNormal.w", 'f', &(SS.sv.e.numNormal.w) }, + { '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) }, { '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 5369880..a0217cb 100644 --- a/graphicswin.cpp +++ b/graphicswin.cpp @@ -54,13 +54,11 @@ const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = { { 1, "Draw Anywhere in 3d\tQ", MNU_FREE_IN_3D, 'Q', mReq }, { 1, NULL, 0, NULL }, { 1, "Datum &Point\tP", MNU_DATUM_POINT, 'P', mReq }, -{ 1, "Datum A&xis\tX", 0, 'X', mReq }, -{ 1, "Datum Pla&ne\tN", 0, 'N', mReq }, -{ 1, "2d Coordinate S&ystem\tY", 0, 'Y', mReq }, +{ 1, "&Workplane (Coordinate S&ystem)\tY", 0, 'Y', mReq }, { 1, NULL, 0, NULL }, { 1, "Line &Segment\tS", MNU_LINE_SEGMENT, 'S', mReq }, { 1, "&Rectangle\tR", MNU_RECTANGLE, 'R', mReq }, -{ 1, "&Circle\tC", 0, 'C', mReq }, +{ 1, "&Circle\tC", MNU_CIRCLE, 'C', mReq }, { 1, "&Arc of a Circle\tA", 0, 'A', mReq }, { 1, "&Cubic Segment\t3", MNU_CUBIC, '3', mReq }, { 1, NULL, 0, NULL }, @@ -107,7 +105,7 @@ void GraphicsWindow::Init(void) { activeWorkplane = r.entity(0); showWorkplanes = true; - showAxes = true; + showNormals = true; showPoints = true; showConstraints = true; showSolids = true; @@ -265,10 +263,7 @@ void GraphicsWindow::MenuEdit(int id) { case MNU_UNSELECT_ALL: HideGraphicsEditControl(); SS.GW.ClearSelection(); - SS.GW.pendingOperation = 0; - SS.GW.pendingDescription = NULL; - SS.GW.pendingPoint.v = 0; - SS.GW.pendingConstraint.v = 0; + SS.GW.ClearPending(); SS.TW.ScreenNavigation('h', 0); SS.TW.Show(); break; @@ -335,7 +330,7 @@ void GraphicsWindow::MenuRequest(int id) { Vector pr, pu; e->WorkplaneGetBasisVectors(&pr, &pu); Quaternion quatf = Quaternion::MakeFrom(pr, pu); - Vector offsetf = SS.GetEntity(e->point[0])->PointGetCoords(); + Vector offsetf = SS.GetEntity(e->point[0])->PointGetNum(); SS.GW.AnimateOnto(quatf, offsetf); SS.GW.EnsureValidActives(); @@ -351,9 +346,10 @@ void GraphicsWindow::MenuRequest(int id) { case MNU_DATUM_POINT: s = "click to place datum point"; goto c; case MNU_LINE_SEGMENT: s = "click first point of line segment"; goto c; case MNU_CUBIC: s = "click first point of cubic segment"; goto c; + case MNU_CIRCLE: s = "click center of circle"; goto c; c: - SS.GW.pendingOperation = id; - SS.GW.pendingDescription = s; + SS.GW.pending.operation = id; + SS.GW.pending.description = s; SS.TW.Show(); break; @@ -361,14 +357,14 @@ c: } } -void GraphicsWindow::UpdateDraggedEntity(hEntity hp, double mx, double my) { +void GraphicsWindow::UpdateDraggedPoint(hEntity hp, double mx, double my) { Entity *p = SS.GetEntity(hp); - Vector pos = p->PointGetCoords(); - UpdateDraggedPoint(&pos, mx, my); + Vector pos = p->PointGetNum(); + UpdateDraggedNum(&pos, mx, my); p->PointForceTo(pos); } -void GraphicsWindow::UpdateDraggedPoint(Vector *pos, double mx, double my) { +void GraphicsWindow::UpdateDraggedNum(Vector *pos, double mx, double my) { *pos = pos->Plus(projRight.ScaledBy((mx - orig.mouse.x)/scale)); *pos = pos->Plus(projUp.ScaledBy((my - orig.mouse.y)/scale)); @@ -384,6 +380,8 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, Point2d mp = { x, y }; + // 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(); @@ -405,7 +403,7 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, NormalizeProjectionVectors(); } else { - double s = 0.3*(PI/180); // degrees per pixel + 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); @@ -421,58 +419,123 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, InvalidateGraphics(); return; } - - // Enforce a bit of static friction before we start dragging. - double dm = orig.mouse.DistanceTo(mp); - if(leftDown && dm > 3 && pendingOperation == 0) { - if(hover.entity.v && - SS.GetEntity(hover.entity)->IsPoint() && - !SS.GetEntity(hover.entity)->PointIsFromReferences()) - { - // Start dragging this point. - ClearSelection(); - pendingPoint = hover.entity; - pendingOperation = DRAGGING_POINT; - } else if(hover.constraint.v && - SS.GetConstraint(hover.constraint)->HasLabel()) - { - ClearSelection(); - pendingConstraint = hover.constraint; - pendingOperation = DRAGGING_CONSTRAINT; - } - } else if(leftDown && pendingOperation == DRAGGING_CONSTRAINT) { - Constraint *c = SS.constraint.FindById(pendingConstraint); - UpdateDraggedPoint(&(c->disp.offset), x, y); - } else if(leftDown && pendingOperation == DRAGGING_POINT) { - if(havePainted) { - UpdateDraggedEntity(pendingPoint, x, y); - SS.GenerateAll(solving == SOLVE_ALWAYS); - havePainted = false; + + 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) { + if(hover.entity.v) { + Entity *e = SS.GetEntity(hover.entity); + if(e->IsPoint()) { + // Start dragging this point. + ClearSelection(); + pending.point = hover.entity; + pending.operation = DRAGGING_POINT; + } else if(e->type == Entity::CIRCLE) { + // 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(hover.constraint.v && + SS.GetConstraint(hover.constraint)->HasLabel()) + { + ClearSelection(); + pending.constraint = hover.constraint; + pending.operation = DRAGGING_CONSTRAINT; + } + } else { + // Otherwise, just hit test and give up + HitTestMakeSelection(mp); } + return; } - // No buttons pressed. - if(pendingOperation == DRAGGING_NEW_POINT || - pendingOperation == DRAGGING_NEW_LINE_POINT) - { - SS.GenerateAll(SS.GW.solving == SOLVE_ALWAYS); - UpdateDraggedEntity(pendingPoint, x, y); - HitTestMakeSelection(mp); - } else if(pendingOperation == DRAGGING_NEW_CUBIC_POINT) { - UpdateDraggedEntity(pendingPoint, x, y); - HitTestMakeSelection(mp); - - hRequest hr = pendingPoint.request(); - Vector p0 = SS.GetEntity(hr.entity(1))->PointGetCoords(); - Vector p3 = SS.GetEntity(hr.entity(4))->PointGetCoords(); - Vector p1 = p0.ScaledBy(2.0/3).Plus(p3.ScaledBy(1.0/3)); - SS.GetEntity(hr.entity(2))->PointForceTo(p1); - Vector p2 = p0.ScaledBy(1.0/3).Plus(p3.ScaledBy(2.0/3)); - SS.GetEntity(hr.entity(3))->PointForceTo(p2); - } else if(!leftDown) { - // Do our usual hit testing, for the selection. + // 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) return; + switch(pending.operation) { + case DRAGGING_CONSTRAINT: { + Constraint *c = SS.constraint.FindById(pending.constraint); + UpdateDraggedNum(&(c->disp.offset), x, y); + break; + } + case DRAGGING_NEW_LINE_POINT: + HitTestMakeSelection(mp); + // and fall through + case DRAGGING_NEW_POINT: + case DRAGGING_POINT: + UpdateDraggedPoint(pending.point, x, y); + break; + + case DRAGGING_NEW_CUBIC_POINT: { + UpdateDraggedPoint(pending.point, x, y); + HitTestMakeSelection(mp); + + hRequest hr = pending.point.request(); + Vector p0 = SS.GetEntity(hr.entity(1))->PointGetNum(); + Vector p3 = SS.GetEntity(hr.entity(4))->PointGetNum(); + Vector p1 = p0.ScaledBy(2.0/3).Plus(p3.ScaledBy(1.0/3)); + SS.GetEntity(hr.entity(2))->PointForceTo(p1); + Vector p2 = p0.ScaledBy(1.0/3).Plus(p3.ScaledBy(2.0/3)); + SS.GetEntity(hr.entity(3))->PointForceTo(p2); + break; + } + case DRAGGING_NEW_RADIUS: + case DRAGGING_RADIUS: { + Entity *circle = SS.GetEntity(pending.circle); + Vector center = SS.GetEntity(circle->point[0])->PointGetNum(); + Point2d c2 = ProjectPoint(center); + SS.GetParam(circle->param[0])->val = c2.DistanceTo(mp)*scale; + break; + } + + case DRAGGING_NORMAL: { + Entity *normal = SS.GetEntity(pending.normal); + Vector p = SS.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 = orig.projRight.Cross(orig.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(orig.projUp, -s*dx); + u = u.RotatedAbout(orig.projRight, s*dy); + v = v.RotatedAbout(orig.projUp, -s*dx); + v = v.RotatedAbout(orig.projRight, s*dy); + } + orig.mouse = mp; + normal->NormalForceTo(Quaternion::MakeFrom(u, v)); + break; + } + + default: oops(); + } + SS.GenerateAll(solving == SOLVE_ALWAYS); + havePainted = false; } bool GraphicsWindow::Selection::Equals(Selection *b) { @@ -493,6 +556,10 @@ void GraphicsWindow::Selection::Draw(void) { if(constraint.v) SS.GetConstraint(constraint)->Draw(); } +void GraphicsWindow::ClearPending(void) { + memset(&pending, 0, sizeof(pending)); +} + void GraphicsWindow::HitTestMakeSelection(Point2d mp) { int i; double d, dmin = 1e12; @@ -503,7 +570,7 @@ void GraphicsWindow::HitTestMakeSelection(Point2d mp) { for(i = 0; i < SS.entity.n; i++) { Entity *e = &(SS.entity.elem[i]); // Don't hover whatever's being dragged. - if(e->h.request().v == pendingPoint.request().v) continue; + if(e->h.request().v == pending.point.request().v) continue; d = e->GetDistance(mp); if(d < 10 && d < dmin) { @@ -599,14 +666,14 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { Constraint::ConstrainCoincident(hover.entity, (p)); \ } hRequest hr; - switch(pendingOperation) { + switch(pending.operation) { case MNU_DATUM_POINT: hr = AddRequest(Request::DATUM_POINT); SS.GetEntity(hr.entity(0))->PointForceTo(v); ClearSelection(); hover.Clear(); - pendingOperation = 0; + pending.operation = 0; break; case MNU_LINE_SEGMENT: @@ -616,10 +683,26 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { ClearSelection(); hover.Clear(); - pendingOperation = DRAGGING_NEW_LINE_POINT; - pendingPoint = hr.entity(2); - pendingDescription = "click to place next point of line"; - SS.GetEntity(pendingPoint)->PointForceTo(v); + pending.operation = DRAGGING_NEW_LINE_POINT; + pending.point = hr.entity(2); + pending.description = "click to place next point of line"; + SS.GetEntity(pending.point)->PointForceTo(v); + break; + + case MNU_CIRCLE: + hr = AddRequest(Request::CIRCLE); + SS.GetEntity(hr.entity(1))->PointForceTo(v); + SS.GetEntity(hr.entity(16))->NormalForceTo( + Quaternion::MakeFrom(SS.GW.projRight, SS.GW.projUp)); + MAYBE_PLACE(hr.entity(1)); + + ClearSelection(); hover.Clear(); + + ClearPending(); + pending.operation = DRAGGING_NEW_RADIUS; + pending.circle = hr.entity(0); + pending.description = "click to set radius"; + SS.GetParam(hr.param(0))->val = 0; break; case MNU_CUBIC: @@ -632,30 +715,28 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { ClearSelection(); hover.Clear(); - pendingOperation = DRAGGING_NEW_CUBIC_POINT; - pendingPoint = hr.entity(4); - pendingDescription = "click to place next point of cubic"; + pending.operation = DRAGGING_NEW_CUBIC_POINT; + pending.point = hr.entity(4); + pending.description = "click to place next point of cubic"; break; + case DRAGGING_RADIUS: case DRAGGING_NEW_POINT: - // The MouseMoved event has already dragged it under the cursor. - pendingOperation = 0; - pendingPoint.v = 0; + // The MouseMoved event has already dragged it as desired. + ClearPending(); break; case DRAGGING_NEW_CUBIC_POINT: if(hover.entity.v && SS.GetEntity(hover.entity)->IsPoint()) { - Constraint::ConstrainCoincident(pendingPoint, hover.entity); + Constraint::ConstrainCoincident(pending.point, hover.entity); } - pendingOperation = 0; - pendingPoint.v = 0; + ClearPending(); break; case DRAGGING_NEW_LINE_POINT: { if(hover.entity.v && SS.GetEntity(hover.entity)->IsPoint()) { - Constraint::ConstrainCoincident(pendingPoint, hover.entity); - pendingOperation = 0; - pendingPoint.v = 0; + Constraint::ConstrainCoincident(pending.point, hover.entity); + ClearPending(); break; } // Create a new line segment, so that we continue drawing. @@ -663,23 +744,20 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { SS.GetEntity(hr.entity(1))->PointForceTo(v); // Constrain the line segments to share an endpoint - Constraint::ConstrainCoincident(pendingPoint, hr.entity(1)); + Constraint::ConstrainCoincident(pending.point, hr.entity(1)); // And drag an endpoint of the new line segment - pendingOperation = DRAGGING_NEW_LINE_POINT; - pendingPoint = hr.entity(2); - pendingDescription = "click to place next point of next line"; - SS.GetEntity(pendingPoint)->PointForceTo(v); + pending.operation = DRAGGING_NEW_LINE_POINT; + pending.point = hr.entity(2); + pending.description = "click to place next point of next line"; + SS.GetEntity(pending.point)->PointForceTo(v); break; } case 0: default: { - pendingOperation = 0; - pendingPoint.v = 0; - pendingConstraint.v = 0; - pendingDescription = NULL; + ClearPending(); if(hover.IsEmpty()) break; @@ -710,12 +788,12 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { } void GraphicsWindow::MouseLeftUp(double mx, double my) { - switch(pendingOperation) { + switch(pending.operation) { case DRAGGING_POINT: case DRAGGING_CONSTRAINT: - pendingOperation = 0; - pendingPoint.v = 0; - pendingConstraint.v = 0; + case DRAGGING_NORMAL: + case DRAGGING_RADIUS: + ClearPending(); break; default: @@ -781,9 +859,9 @@ void GraphicsWindow::ToggleBool(int link, DWORD v) { } void GraphicsWindow::ToggleAnyDatumShown(int link, DWORD v) { - bool t = !(SS.GW.showWorkplanes && SS.GW.showAxes && SS.GW.showPoints); + bool t = !(SS.GW.showWorkplanes && SS.GW.showNormals && SS.GW.showPoints); SS.GW.showWorkplanes = t; - SS.GW.showAxes = t; + SS.GW.showNormals = t; SS.GW.showPoints = t; SS.GenerateAll(SS.GW.solving == SOLVE_ALWAYS); @@ -809,7 +887,7 @@ void GraphicsWindow::Paint(int w, int h) { glMatrixMode(GL_PROJECTION); glLoadIdentity(); - glScaled(scale*2.0/w, scale*2.0/h, scale*2.0/w); + glScaled(scale*2.0/w, scale*2.0/h, scale*1.0/50000); double tx = projRight.Dot(offset); double ty = projUp.Dot(offset); diff --git a/sketch.cpp b/sketch.cpp index d51abbe..15c8535 100644 --- a/sketch.cpp +++ b/sketch.cpp @@ -68,7 +68,7 @@ void Group::Generate(IdList *entity, Entity *e = &(entity->elem[i]); if(e->group.v != opA.v) continue; - CopyEntity(e->h, 0, h.param(0), h.param(1), h.param(2)); + CopyEntity(e->h, 0, h.param(0), h.param(1), h.param(2), true); } break; @@ -93,7 +93,9 @@ hEntity Group::Remap(hEntity in, int copyNumber) { return h.entity(em.h.v); } -void Group::CopyEntity(hEntity in, int a, hParam dx, hParam dy, hParam dz) { +void Group::CopyEntity(hEntity in, int a, hParam dx, hParam dy, hParam dz, + bool isExtrusion) +{ Entity *ep = SS.GetEntity(in); Entity en; @@ -119,13 +121,36 @@ void Group::CopyEntity(hEntity in, int a, hParam dx, hParam dy, hParam dz) { en.point[3] = Remap(ep->point[3], a); break; + case Entity::CIRCLE: + en.point[0] = Remap(ep->point[0], a); + en.normal = Remap(ep->normal, a); + en.param[0] = ep->param[0]; // XXX make numerical somehow later + break; + case Entity::POINT_IN_3D: case Entity::POINT_IN_2D: en.type = Entity::POINT_XFRMD; - en.point[0] = ep->h; en.param[0] = dx; en.param[1] = dy; en.param[2] = dz; + en.numPoint = ep->PointGetNum(); + + if(isExtrusion) { + if(a != 0) oops(); + SS.entity.Add(&en); + en.point[0] = ep->h; + en.point[1] = en.h; + en.h = Remap(ep->h, 1); + en.type = Entity::LINE_SEGMENT; + // And then this line segment gets added + } + break; + + case Entity::NORMAL_IN_3D: + case Entity::NORMAL_IN_2D: + en.type = Entity::NORMAL_XFRMD; + en.numNormal = ep->NormalGetNum(); + en.point[0] = Remap(ep->point[0], a); break; default: @@ -242,67 +267,104 @@ void Request::Generate(IdList *entity, int points = 0; int params = 0; int et = 0; + bool hasNormal = false; int i; - Group *g = SS.group.FindById(group); - Entity e; memset(&e, 0, sizeof(e)); switch(type) { case Request::WORKPLANE: - et = Entity::WORKPLANE; points = 1; params = 4; goto c; + et = Entity::WORKPLANE; + points = 1; + hasNormal = true; + break; case Request::DATUM_POINT: - et = 0; points = 1; params = 0; goto c; + et = 0; + points = 1; + break; case Request::LINE_SEGMENT: - et = Entity::LINE_SEGMENT; points = 2; params = 0; goto c; + et = Entity::LINE_SEGMENT; + points = 2; + break; + + case Request::CIRCLE: + et = Entity::CIRCLE; + points = 1; + params = 1; + hasNormal = true; + break; case Request::CUBIC: - et = Entity::CUBIC; points = 4; params = 0; goto c; -c: { - // Generate the entity that's specific to this request. - e.type = et; - e.group = group; - e.h = h.entity(0); - - // And generate entities for the points - for(i = 0; i < points; i++) { - Entity p; - memset(&p, 0, sizeof(p)); - p.workplane = workplane; - // points start from entity 1, except for datum point case - p.h = h.entity(i+(et ? 1 : 0)); - p.group = group; - - if(workplane.v == Entity::FREE_IN_3D.v) { - p.type = Entity::POINT_IN_3D; - // params for x y z - p.param[0] = AddParam(param, h.param(16 + 3*i + 0)); - p.param[1] = AddParam(param, h.param(16 + 3*i + 1)); - p.param[2] = AddParam(param, h.param(16 + 3*i + 2)); - } else { - p.type = Entity::POINT_IN_2D; - // params for u v - p.param[0] = AddParam(param, h.param(16 + 3*i + 0)); - p.param[1] = AddParam(param, h.param(16 + 3*i + 1)); - } - entity->Add(&p); - e.point[i] = p.h; - } - // And generate any params not associated with the point that - // we happen to need. - for(i = 0; i < params; i++) { - e.param[i] = AddParam(param, h.param(i)); - } - - if(et) entity->Add(&e); + et = Entity::CUBIC; + points = 4; break; - } - default: - oops(); + default: oops(); } + + // Generate the entity that's specific to this request. + e.type = et; + e.group = group; + e.workplane = workplane; + e.h = h.entity(0); + + // And generate entities for the points + for(i = 0; i < points; i++) { + Entity p; + memset(&p, 0, sizeof(p)); + p.workplane = workplane; + // points start from entity 1, except for datum point case + p.h = h.entity(i+(et ? 1 : 0)); + p.group = group; + + if(workplane.v == Entity::FREE_IN_3D.v) { + p.type = Entity::POINT_IN_3D; + // params for x y z + p.param[0] = AddParam(param, h.param(16 + 3*i + 0)); + p.param[1] = AddParam(param, h.param(16 + 3*i + 1)); + p.param[2] = AddParam(param, h.param(16 + 3*i + 2)); + } else { + p.type = Entity::POINT_IN_2D; + // params for u v + p.param[0] = AddParam(param, h.param(16 + 3*i + 0)); + p.param[1] = AddParam(param, h.param(16 + 3*i + 1)); + } + entity->Add(&p); + e.point[i] = p.h; + } + if(hasNormal) { + Entity n; + memset(&n, 0, sizeof(n)); + n.workplane = workplane; + n.h = h.entity(16); + n.group = group; + if(workplane.v == Entity::FREE_IN_3D.v) { + n.type = Entity::NORMAL_IN_3D; + n.param[0] = AddParam(param, h.param(32+0)); + n.param[1] = AddParam(param, h.param(32+1)); + n.param[2] = AddParam(param, h.param(32+2)); + n.param[3] = AddParam(param, h.param(32+3)); + } else { + n.type = Entity::NORMAL_IN_2D; + // and this is just a copy of the workplane quaternion, + // so no params required + } + if(points < 1) oops(); + // The point determines where the normal gets displayed on-screen; + // it's entirely cosmetic. + n.point[0] = e.point[0]; + entity->Add(&n); + e.normal = n.h; + } + // And generate any params not associated with the point that + // we happen to need. + for(i = 0; i < params; i++) { + e.param[i] = AddParam(param, h.param(i)); + } + + if(et) entity->Add(&e); } char *Request::DescriptionString(void) { diff --git a/sketch.h b/sketch.h index 9c7b07c..d8b42ce 100644 --- a/sketch.h +++ b/sketch.h @@ -103,7 +103,8 @@ public: // mapping list. IdList remap; hEntity Remap(hEntity in, int copyNumber); - void CopyEntity(hEntity in, int a, hParam dx, hParam dy, hParam dz); + void CopyEntity(hEntity in, int a, hParam dx, hParam dy, hParam dz, + bool isExtrusion); void MakePolygons(void); void Draw(void); @@ -130,6 +131,7 @@ public: static const int DATUM_POINT = 101; static const int LINE_SEGMENT = 200; static const int CUBIC = 300; + static const int CIRCLE = 400; int type; @@ -152,22 +154,30 @@ public: static const hEntity FREE_IN_3D; - static const int WORKPLANE = 1000; static const int POINT_IN_3D = 2000; static const int POINT_IN_2D = 2001; static const int POINT_XFRMD = 2010; - static const int LINE_SEGMENT = 10000; - static const int CUBIC = 11000; - static const int EDGE_LIST = 90000; - static const int FACE_LIST = 91000; + static const int NORMAL_IN_3D = 3000; + static const int NORMAL_IN_2D = 3001; + static const int NORMAL_XFRMD = 3010; + + static const int WORKPLANE = 10000; + static const int LINE_SEGMENT = 11000; + static const int CUBIC = 12000; + static const int CIRCLE = 13000; + int type; // When it comes time to draw an entity, we look here to get the // defining variables. hParam param[4]; hEntity point[4]; - hEntity direction; + hEntity normal; + + // Derived points are a symbolic offset from a constant base. + Vector numPoint; + Quaternion numNormal; hGroup group; hEntity workplane; // or Entity::FREE_IN_3D @@ -184,12 +194,18 @@ public: bool IsPoint(void); // Applies for any of the point types - Vector PointGetCoords(void); + Vector PointGetNum(void); ExprVector PointGetExprs(void); void PointGetExprsInWorkplane(hEntity wrkpl, Expr **u, Expr **v); void PointForceTo(Vector v); bool PointIsFromReferences(void); + bool IsNormal(void); + // Applies for any of the normal types + Quaternion NormalGetNum(void); + ExprQuaternion NormalGetExprs(void); + void NormalForceTo(Quaternion q); + // Applies for anything that comes with a plane bool HasPlane(void); // The plane is points P such that P dot (xn, yn, zn) - d = 0 diff --git a/solvespace.cpp b/solvespace.cpp index 4f203e3..926b9aa 100644 --- a/solvespace.cpp +++ b/solvespace.cpp @@ -72,28 +72,29 @@ void SolveSpace::ForceReferences(void) { // Force the values of the paramters that define the three reference // coordinate systems. static const struct { - hRequest hr; - double a, b, c, d; + hRequest hr; + Quaternion q; } Quat[] = { - { Request::HREQUEST_REFERENCE_XY, 1, 0, 0, 0, }, - { Request::HREQUEST_REFERENCE_YZ, 0.5, 0.5, 0.5, 0.5, }, - { Request::HREQUEST_REFERENCE_ZX, 0.5, -0.5, -0.5, -0.5, }, + { Request::HREQUEST_REFERENCE_XY, { 1, 0, 0, 0, } }, + { Request::HREQUEST_REFERENCE_YZ, { 0.5, 0.5, 0.5, 0.5, } }, + { Request::HREQUEST_REFERENCE_ZX, { 0.5, -0.5, -0.5, -0.5, } }, }; for(int i = 0; i < 3; i++) { hRequest hr = Quat[i].hr; + Entity *wrkpl = GetEntity(hr.entity(0)); // The origin for our coordinate system, always zero - Vector v = Vector::MakeFrom(0, 0, 0); - Entity *origin = GetEntity(hr.entity(1)); - origin->PointForceTo(v); + Entity *origin = GetEntity(wrkpl->point[0]); + origin->PointForceTo(Vector::MakeFrom(0, 0, 0)); GetParam(origin->param[0])->known = true; GetParam(origin->param[1])->known = true; GetParam(origin->param[2])->known = true; // The quaternion that defines the rotation, from the table. - Param *p; - p = GetParam(hr.param(0)); p->val = Quat[i].a; p->known = true; - p = GetParam(hr.param(1)); p->val = Quat[i].b; p->known = true; - p = GetParam(hr.param(2)); p->val = Quat[i].c; p->known = true; - p = GetParam(hr.param(3)); p->val = Quat[i].d; p->known = true; + Entity *normal = GetEntity(wrkpl->normal); + normal->NormalForceTo(Quat[i].q); + GetParam(normal->param[0])->known = true; + GetParam(normal->param[1])->known = true; + GetParam(normal->param[2])->known = true; + GetParam(normal->param[3])->known = true; } } diff --git a/solvespace.h b/solvespace.h index d1d1466..f7cc433 100644 --- a/solvespace.h +++ b/solvespace.h @@ -27,6 +27,7 @@ typedef signed long SDWORD; class Expr; class ExprVector; +class ExprQuaternion; // From the platform-specific code. int SaveFileYesNoCancel(void); diff --git a/system.cpp b/system.cpp index 6668165..57cba6a 100644 --- a/system.cpp +++ b/system.cpp @@ -86,8 +86,8 @@ void System::SortBySensitivity(void) { mat.dragged[j] = false; mat.permutation[j] = j; } - if(SS.GW.pendingPoint.v) { - Entity *p = SS.entity.FindByIdNoOops(SS.GW.pendingPoint); + if(SS.GW.pending.point.v) { + Entity *p = SS.entity.FindByIdNoOops(SS.GW.pending.point); // If we're solving an earlier group, then the pending point might // not exist in the entity tables yet. if(p) { diff --git a/textwin.cpp b/textwin.cpp index 21b9ae0..4efebb8 100644 --- a/textwin.cpp +++ b/textwin.cpp @@ -158,15 +158,15 @@ done: } void TextWindow::Show(void) { - if(!(SS.GW.pendingOperation)) SS.GW.pendingDescription = NULL; + if(!(SS.GW.pending.operation)) SS.GW.ClearPending(); ShowHeader(); - if(SS.GW.pendingDescription) { + if(SS.GW.pending.description) { // A pending operation (that must be completed with the mouse in // the graphics window) will preempt our usual display. Printf(false, ""); - Printf(false, "%s", SS.GW.pendingDescription); + Printf(false, "%s", SS.GW.pending.description); } else { switch(shown->screen) { default: @@ -222,7 +222,7 @@ void TextWindow::ShowHeader(void) { SS.GetEntity(SS.GW.activeWorkplane)->DescriptionString(); // Navigation buttons - if(SS.GW.pendingDescription) { + if(SS.GW.pending.description) { Printf(false, " %Bt%Ft workplane:%Fd %s", cd); } else { Printf(false, " %Lb%f<<%E %Lh%fhome%E %Bt%Ft workplane:%Fd %s", @@ -232,9 +232,9 @@ void TextWindow::ShowHeader(void) { } int datumColor; - if(SS.GW.showWorkplanes && SS.GW.showAxes && SS.GW.showPoints) { + if(SS.GW.showWorkplanes && SS.GW.showNormals && SS.GW.showPoints) { datumColor = 's'; // shown - } else if(!(SS.GW.showWorkplanes || SS.GW.showAxes || SS.GW.showPoints)) { + } else if(!(SS.GW.showWorkplanes || SS.GW.showNormals || SS.GW.showPoints)){ datumColor = 'h'; // hidden } else { datumColor = 'm'; // mixed @@ -243,11 +243,11 @@ void TextWindow::ShowHeader(void) { #define hs(b) ((b) ? 's' : 'h') Printf(false, "%Bt%Ftshow: " "%Fp%Ll%D%fworkplanes%E " - "%Fp%Ll%D%fvectors%E " + "%Fp%Ll%D%fnormals%E " "%Fp%Ll%D%fpoints%E " "%Fp%Ll%fany-datum%E", hs(SS.GW.showWorkplanes), (DWORD)&(SS.GW.showWorkplanes), &(SS.GW.ToggleBool), - hs(SS.GW.showAxes), (DWORD)&(SS.GW.showAxes), &(SS.GW.ToggleBool), + hs(SS.GW.showNormals), (DWORD)&(SS.GW.showNormals), &(SS.GW.ToggleBool), hs(SS.GW.showPoints), (DWORD)&(SS.GW.showPoints), &(SS.GW.ToggleBool), datumColor, &(SS.GW.ToggleAnyDatumShown) ); diff --git a/ui.h b/ui.h index ef9871d..b199bee 100644 --- a/ui.h +++ b/ui.h @@ -103,6 +103,7 @@ public: MNU_FREE_IN_3D, MNU_DATUM_POINT, MNU_LINE_SEGMENT, + MNU_CIRCLE, MNU_RECTANGLE, MNU_CUBIC, // Group @@ -169,20 +170,31 @@ public: void EnsureValidActives(); // Operations that must be completed by doing something with the mouse - // are noted here. + // are noted here. These occupy the same space as the menu ids. + static const int FIRST_PENDING = 0x0f000000; static const int DRAGGING_POINT = 0x0f000000; static const int DRAGGING_NEW_POINT = 0x0f000001; static const int DRAGGING_NEW_LINE_POINT = 0x0f000002; static const int DRAGGING_NEW_CUBIC_POINT = 0x0f000003; static const int DRAGGING_CONSTRAINT = 0x0f000004; - hEntity pendingPoint; - hConstraint pendingConstraint; - int pendingOperation; - char *pendingDescription; - hRequest AddRequest(int type); + static const int DRAGGING_RADIUS = 0x0f000005; + static const int DRAGGING_NORMAL = 0x0f000006; + static const int DRAGGING_NEW_RADIUS = 0x0f000007; + struct { + int operation; + hEntity point; + hEntity circle; + hEntity normal; + hConstraint constraint; + + char *description; + } pending; + void ClearPending(void); // The constraint that is being edited with the on-screen textbox. hConstraint constraintBeingEdited; + + hRequest AddRequest(int type); // The current selection. class Selection { @@ -215,7 +227,7 @@ public: // This sets what gets displayed. bool showWorkplanes; - bool showAxes; + bool showNormals; bool showPoints; bool showConstraints; bool showTextWindow; @@ -228,8 +240,8 @@ public: static const int SOLVE_ALWAYS = 1; int solving; - void UpdateDraggedPoint(Vector *pos, double mx, double my); - void UpdateDraggedEntity(hEntity hp, double mx, double my); + void UpdateDraggedNum(Vector *pos, double mx, double my); + void UpdateDraggedPoint(hEntity hp, double mx, double my); // These are called by the platform-specific code. void Paint(int w, int h); diff --git a/util.cpp b/util.cpp index 8875bf9..bfe2987 100644 --- a/util.cpp +++ b/util.cpp @@ -23,12 +23,12 @@ void MakeMatrix(double *mat, double a11, double a12, double a13, double a14, mat[15] = a44; } -Quaternion Quaternion::MakeFrom(double a, double b, double c, double d) { +Quaternion Quaternion::MakeFrom(double w, double vx, double vy, double vz) { Quaternion q; - q.a = a; - q.b = b; - q.c = c; - q.d = d; + q.w = w; + q.vx = vx; + q.vy = vy; + q.vz = vz; return q; } @@ -40,65 +40,65 @@ Quaternion Quaternion::MakeFrom(Vector u, Vector v) double s, tr = 1 + u.x + v.y + n.z; if(tr > 1e-4) { s = 2*sqrt(tr); - q.a = s/4; - q.b = (v.z - n.y)/s; - q.c = (n.x - u.z)/s; - q.d = (u.y - v.x)/s; + q.w = s/4; + q.vx = (v.z - n.y)/s; + q.vy = (n.x - u.z)/s; + q.vz = (u.y - v.x)/s; } else { double m = max(u.x, max(v.y, n.z)); if(m == u.x) { s = 2*sqrt(1 + u.x - v.y - n.z); - q.a = (v.z - n.y)/s; - q.b = s/4; - q.c = (u.y + v.x)/s; - q.d = (n.x + u.z)/s; + q.w = (v.z - n.y)/s; + q.vx = s/4; + q.vy = (u.y + v.x)/s; + q.vz = (n.x + u.z)/s; } else if(m == v.y) { s = 2*sqrt(1 - u.x + v.y - n.z); - q.a = (n.x - u.z)/s; - q.b = (u.y + v.x)/s; - q.c = s/4; - q.d = (v.z + n.y)/s; + q.w = (n.x - u.z)/s; + q.vx = (u.y + v.x)/s; + q.vy = s/4; + q.vz = (v.z + n.y)/s; } else if(m == n.z) { s = 2*sqrt(1 - u.x - v.y + n.z); - q.a = (u.y - v.x)/s; - q.b = (n.x + u.z)/s; - q.c = (v.z + n.y)/s; - q.d = s/4; + q.w = (u.y - v.x)/s; + q.vx = (n.x + u.z)/s; + q.vy = (v.z + n.y)/s; + q.vz = s/4; } else oops(); } return q.WithMagnitude(1); } -Quaternion Quaternion::Plus(Quaternion y) { +Quaternion Quaternion::Plus(Quaternion b) { Quaternion q; - q.a = a + y.a; - q.b = b + y.b; - q.c = c + y.c; - q.d = d + y.d; + q.w = w + b.w; + q.vx = vx + b.vx; + q.vy = vy + b.vy; + q.vz = vz + b.vz; return q; } -Quaternion Quaternion::Minus(Quaternion y) { +Quaternion Quaternion::Minus(Quaternion b) { Quaternion q; - q.a = a - y.a; - q.b = b - y.b; - q.c = c - y.c; - q.d = d - y.d; + q.w = w - b.w; + q.vx = vx - b.vx; + q.vy = vy - b.vy; + q.vz = vz - b.vz; return q; } Quaternion Quaternion::ScaledBy(double s) { Quaternion q; - q.a = a*s; - q.b = b*s; - q.c = c*s; - q.d = d*s; + q.w = w*s; + q.vx = vx*s; + q.vy = vy*s; + q.vz = vz*s; return q; } double Quaternion::Magnitude(void) { - return sqrt(a*a + b*b + c*c + d*d); + return sqrt(w*w + vx*vx + vy*vy + vz*vz); } Quaternion Quaternion::WithMagnitude(double s) { @@ -107,20 +107,24 @@ Quaternion Quaternion::WithMagnitude(double s) { Vector Quaternion::RotationU(void) { Vector v; - v.x = a*a + b*b - c*c - d*d; - v.y = 2*a*d + 2*b*c; - v.z = 2*b*d - 2*a*c; + v.x = w*w + vx*vx - vy*vy - vz*vz; + v.y = 2*w *vz + 2*vx*vy; + v.z = 2*vx*vz - 2*w *vy; return v; } Vector Quaternion::RotationV(void) { Vector v; - v.x = 2*b*c - 2*a*d; - v.y = a*a - b*b + c*c - d*d; - v.z = 2*a*b + 2*c*d; + v.x = 2*vx*vy - 2*w*vz; + v.y = w*w - vx*vx + vy*vy - vz*vz; + v.z = 2*w*vx + 2*vy*vz; return v; } +Vector Quaternion::RotationN(void) { + return RotationU().Cross(RotationV()); +} + Vector Vector::MakeFrom(double x, double y, double z) { Vector v; @@ -199,17 +203,13 @@ Vector Vector::Normal(int which) { n.z = 0; n.x = y; n.y = -x; - } else { - oops(); - } + } else oops(); if(which == 0) { // That's the vector we return. } else if(which == 1) { n = this->Cross(n); - } else { - oops(); - } + } else oops(); n = n.WithMagnitude(1);