Add horizontal and vertical constraints. Those have their own 2d/3d

issues, when the points are not all in the same coordinate system.
All painful, of course. Also add continuous line drawing, and
auto-constraining of line segments as I draw.

[git-p4: depot-paths = "//depot/solvespace/": change = 1683]
This commit is contained in:
Jonathan Westhues 2008-04-22 23:29:19 -08:00
parent 1bf7e3deaf
commit a8001adf33
8 changed files with 260 additions and 44 deletions

View File

@ -5,6 +5,16 @@ hConstraint Constraint::AddConstraint(Constraint *c) {
return c->h; return c->h;
} }
void Constraint::ConstrainCoincident(hEntity ptA, hEntity ptB) {
Constraint c;
memset(&c, 0, sizeof(c));
c.group = SS.GW.activeGroup;
c.type = Constraint::POINTS_COINCIDENT;
c.ptA = ptA;
c.ptB = ptB;
SS.constraint.AddAndAssignId(&c);
}
void Constraint::MenuConstrain(int id) { void Constraint::MenuConstrain(int id) {
Constraint c; Constraint c;
memset(&c, 0, sizeof(c)); memset(&c, 0, sizeof(c));
@ -62,6 +72,48 @@ void Constraint::MenuConstrain(int id) {
AddConstraint(&c); AddConstraint(&c);
break; break;
case GraphicsWindow::MNU_VERTICAL:
case GraphicsWindow::MNU_HORIZONTAL: {
hEntity ha, hb;
if(gs.lineSegments == 1 && gs.n == 1) {
c.entityA = gs.entity[0];
Entity *e = SS.GetEntity(c.entityA);
ha = e->assoc[0];
hb = e->assoc[1];
} else if(gs.points == 2 && gs.n == 2) {
ha = c.ptA = gs.point[0];
hb = c.ptB = gs.point[1];
} else {
Error("Bad selection for horizontal / vertical constraint.");
return;
}
Entity *ea = SS.GetEntity(ha);
Entity *eb = SS.GetEntity(hb);
if(ea->csys.v == Entity::NO_CSYS.v &&
eb->csys.v == Entity::NO_CSYS.v)
{
Error("Horizontal/vertical constraint applies only to "
"entities drawn in a 2d coordinate system.");
return;
}
if(eb->csys.v == SS.GW.activeCsys.v) {
// We are constraining two points in two different csyss; so
// we have two choices for the definitons of the coordinate
// directions. ptA's gets chosen, so make sure that's the
// active csys.
hEntity t = c.ptA;
c.ptA = c.ptB;
c.ptB = t;
}
if(id == GraphicsWindow::MNU_HORIZONTAL) {
c.type = HORIZONTAL;
} else {
c.type = VERTICAL;
}
AddConstraint(&c);
break;
}
case GraphicsWindow::MNU_SOLVE_NOW: case GraphicsWindow::MNU_SOLVE_NOW:
SS.Solve(); SS.Solve();
return; return;
@ -157,24 +209,27 @@ void Constraint::Generate(IdList<Equation,hEquation> *l) {
AddEq(l, eab.x, 0); AddEq(l, eab.x, 0);
AddEq(l, eab.y, 1); AddEq(l, eab.y, 1);
AddEq(l, eab.z, 2); AddEq(l, eab.z, 2);
} else if(a->IsPointIn3d() && !b->IsPointIn3d()) { } else if(!(a->IsPointIn3d() || b->IsPointIn3d()) &&
// One point has 2 DOF, one has 3; write two eqs, on the (a->csys.v == b->csys.v))
// projection of the 3 DOF point into the 2 DOF point plane. {
ExprVector p3;
p3 = a->PointGetExprs();
Entity *csy = SS.GetEntity(b->csys);
ExprVector u, v;
csy->Csys2dGetBasisExprs(&u, &v);
AddEq(l, Expr::FromParam(b->param.h[0])->Minus(p3.Dot(u)), 0);
AddEq(l, Expr::FromParam(b->param.h[1])->Minus(p3.Dot(v)), 1);
} else if(a->csys.v == b->csys.v) {
// Both in same csys, nice. // Both in same csys, nice.
AddEq(l, Expr::FromParam(a->param.h[0])->Minus( AddEq(l, Expr::FromParam(a->param.h[0])->Minus(
Expr::FromParam(b->param.h[0])), 0); Expr::FromParam(b->param.h[0])), 0);
AddEq(l, Expr::FromParam(a->param.h[1])->Minus( AddEq(l, Expr::FromParam(a->param.h[1])->Minus(
Expr::FromParam(b->param.h[1])), 1); Expr::FromParam(b->param.h[1])), 1);
} else { } else {
oops(); // Either two 2 DOF points in different planes, or one
// 3 DOF point and one 2 DOF point. Either way, write two
// equations on the projection of a into b's plane.
ExprVector p3;
p3 = a->PointGetExprs();
Entity *csy = SS.GetEntity(b->csys);
ExprVector offset = csy->Csys2dGetOffsetExprs();
p3 = p3.Minus(offset);
ExprVector u, v;
csy->Csys2dGetBasisExprs(&u, &v);
AddEq(l, Expr::FromParam(b->param.h[0])->Minus(p3.Dot(u)), 0);
AddEq(l, Expr::FromParam(b->param.h[1])->Minus(p3.Dot(v)), 1);
} }
break; break;
} }
@ -188,6 +243,41 @@ void Constraint::Generate(IdList<Equation,hEquation> *l) {
break; break;
} }
case HORIZONTAL:
case VERTICAL: {
hEntity ha, hb;
if(entityA.v) {
Entity *e = SS.GetEntity(entityA);
ha = e->assoc[0];
hb = e->assoc[1];
} else {
ha = ptA;
hb = ptB;
}
Entity *a = SS.GetEntity(ha);
Entity *b = SS.GetEntity(hb);
if(a->csys.v == Entity::NO_CSYS.v) {
Entity *t = a;
a = b;
b = t;
}
if(a->csys.v == b->csys.v) {
int i = (type == HORIZONTAL) ? 1 : 0;
AddEq(l, Expr::FromParam(a->param.h[i])->Minus(
Expr::FromParam(b->param.h[i])), 0);
} else {
Entity *csy = SS.GetEntity(a->csys);
ExprVector u, v;
csy->Csys2dGetBasisExprs(&u, &v);
ExprVector norm = (type == HORIZONTAL) ? v : u;
ExprVector pa = a->PointGetExprs();
ExprVector pb = b->PointGetExprs();
AddEq(l, (pa.Minus(pb)).Dot(norm), 0);
}
break;
}
default: oops(); default: oops();
} }
} }

View File

@ -33,7 +33,7 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) {
Vector gu = SS.GW.projUp; Vector gu = SS.GW.projUp;
Vector gn = gr.Cross(gu); Vector gn = gr.Cross(gu);
glxColor(1, 0.3, 1); glxColor(1, 0.2, 1);
switch(type) { switch(type) {
case PT_PT_DISTANCE: { case PT_PT_DISTANCE: {
Vector ap = SS.GetEntity(ptA)->PointGetCoords(); Vector ap = SS.GetEntity(ptA)->PointGetCoords();
@ -125,6 +125,55 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) {
break; break;
} }
case HORIZONTAL:
case VERTICAL:
if(entityA.v) {
Entity *e = SS.GetEntity(entityA);
Vector a = SS.GetEntity(e->assoc[0])->PointGetCoords();
Vector b = SS.GetEntity(e->assoc[1])->PointGetCoords();
Vector m = (a.ScaledBy(0.5)).Plus(b.ScaledBy(0.5));
if(dogd.drawing) {
glPushMatrix();
glxTranslatev(m);
glxOntoCsys(gr, gu);
glxWriteText(type == HORIZONTAL ? "H" : "V");
glPopMatrix();
} else {
Point2d ref = SS.GW.ProjectPoint(m);
dogd.dmin = min(dogd.dmin, ref.DistanceTo(dogd.mp)-10);
}
} else {
Vector a = SS.GetEntity(ptA)->PointGetCoords();
Vector b = SS.GetEntity(ptB)->PointGetCoords();
Entity *csy = SS.GetEntity(SS.GetEntity(ptA)->csys);
Vector cn = csy->Csys2dGetNormalVector();
int i;
for(i = 0; i < 2; i++) {
Vector o = (i == 0) ? a : b;
Vector d = (i == 0) ? a.Minus(b) : b.Minus(a);
Vector dp = cn.Cross(d);
d = d.WithMagnitude(14/SS.GW.scale);
Vector c = o.Minus(d);
LineDrawOrGetDistance(o, c);
d = d.WithMagnitude(3/SS.GW.scale);
dp = dp.WithMagnitude(2/SS.GW.scale);
if(dogd.drawing) {
glBegin(GL_QUADS);
glxVertex3v((c.Plus(d)).Plus(dp));
glxVertex3v((c.Minus(d)).Plus(dp));
glxVertex3v((c.Minus(d)).Minus(dp));
glxVertex3v((c.Plus(d)).Minus(dp));
glEnd();
} else {
Point2d ref = SS.GW.ProjectPoint(c);
dogd.dmin = min(dogd.dmin, ref.DistanceTo(dogd.mp)-6);
}
}
}
break;
default: oops(); default: oops();
} }
} }

View File

@ -16,6 +16,12 @@ void Entity::Csys2dGetBasisVectors(Vector *u, Vector *v) {
*v = quat.RotationV(); *v = quat.RotationV();
} }
Vector Entity::Csys2dGetNormalVector(void) {
Vector u, v;
Csys2dGetBasisVectors(&u, &v);
return u.Cross(v);
}
void Entity::Csys2dGetBasisExprs(ExprVector *u, ExprVector *v) { void Entity::Csys2dGetBasisExprs(ExprVector *u, ExprVector *v) {
Expr *a = Expr::FromParam(param.h[0]); Expr *a = Expr::FromParam(param.h[0]);
Expr *b = Expr::FromParam(param.h[1]); Expr *b = Expr::FromParam(param.h[1]);
@ -47,6 +53,10 @@ void Entity::Csys2dGetBasisExprs(ExprVector *u, ExprVector *v) {
v->z = (v->z)->Plus(two->Times(c->Times(d))); v->z = (v->z)->Plus(two->Times(c->Times(d)));
} }
ExprVector Entity::Csys2dGetOffsetExprs(void) {
return SS.GetEntity(assoc[0])->PointGetExprs();
}
bool Entity::HasPlane(void) { bool Entity::HasPlane(void) {
switch(type) { switch(type) {
case CSYS_2D: case CSYS_2D:

View File

@ -73,8 +73,8 @@ const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = {
{ 1, "A&ngle\tShift+N", 0, 'N'|S, NULL }, { 1, "A&ngle\tShift+N", 0, 'N'|S, NULL },
{ 1, "Other S&upplementary Angle\tShift+U", 0, 'U'|S, NULL }, { 1, "Other S&upplementary Angle\tShift+U", 0, 'U'|S, NULL },
{ 1, NULL, 0, NULL }, { 1, NULL, 0, NULL },
{ 1, "&Horizontal\tShift+H", 0, 'H'|S, NULL }, { 1, "&Horizontal\tShift+H", MNU_HORIZONTAL, 'H'|S, mCon },
{ 1, "&Vertical\tShift+V", 0, 'V'|S, NULL }, { 1, "&Vertical\tShift+V", MNU_VERTICAL, 'V'|S, mCon },
{ 1, NULL, 0, NULL }, { 1, NULL, 0, NULL },
{ 1, "&On Point / Curve / Plane\tShift+O", MNU_ON_ENTITY, 'O'|S, mCon }, { 1, "&On Point / Curve / Plane\tShift+O", MNU_ON_ENTITY, 'O'|S, mCon },
{ 1, "E&qual Length / Radius\tShift+Q", MNU_EQUAL, 'Q'|S, mCon }, { 1, "E&qual Length / Radius\tShift+Q", MNU_EQUAL, 'Q'|S, mCon },
@ -264,6 +264,8 @@ void GraphicsWindow::MenuEdit(int id) {
SS.GW.ClearSelection(); SS.GW.ClearSelection();
SS.GW.pendingOperation = 0; SS.GW.pendingOperation = 0;
SS.GW.pendingDescription = NULL; SS.GW.pendingDescription = NULL;
SS.GW.pendingPoint.v = 0;
SS.GW.pendingConstraint.v = 0;
SS.TW.ScreenNavigation('h', 0); SS.TW.ScreenNavigation('h', 0);
SS.TW.Show(); SS.TW.Show();
break; break;
@ -360,7 +362,7 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown,
double dy = (y - orig.mouse.y) / scale; double dy = (y - orig.mouse.y) / scale;
// When the view is locked, permit only translation (pan). // When the view is locked, permit only translation (pan).
if(shiftDown || viewLocked) { if(!(shiftDown || ctrlDown) || viewLocked) {
offset.x = orig.offset.x + dx*projRight.x + dy*projUp.x; offset.x = orig.offset.x + dx*projRight.x + dy*projUp.x;
offset.y = orig.offset.y + dx*projRight.y + dy*projUp.y; offset.y = orig.offset.y + dx*projRight.y + dy*projUp.y;
offset.z = orig.offset.z + dx*projRight.z + dy*projUp.z; offset.z = orig.offset.z + dx*projRight.z + dy*projUp.z;
@ -404,7 +406,7 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown,
// Start dragging this point. // Start dragging this point.
ClearSelection(); ClearSelection();
pendingPoint = hover.entity; pendingPoint = hover.entity;
pendingOperation = PENDING_OPERATION_DRAGGING_POINT; pendingOperation = DRAGGING_POINT;
} }
} else if(hover.constraint.v && } else if(hover.constraint.v &&
SS.GetConstraint(hover.constraint)->HasLabel()) SS.GetConstraint(hover.constraint)->HasLabel())
@ -412,29 +414,28 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown,
if(dm > dmt) { if(dm > dmt) {
ClearSelection(); ClearSelection();
pendingConstraint = hover.constraint; pendingConstraint = hover.constraint;
pendingOperation = PENDING_OPERATION_DRAGGING_CONSTRAINT; pendingOperation = DRAGGING_CONSTRAINT;
} }
} }
} else if(pendingOperation == PENDING_OPERATION_DRAGGING_POINT || } else if(pendingOperation == DRAGGING_POINT ||
pendingOperation == PENDING_OPERATION_DRAGGING_NEW_POINT) pendingOperation == DRAGGING_NEW_POINT ||
pendingOperation == DRAGGING_NEW_LINE_POINT)
{ {
UpdateDraggedEntity(pendingPoint, x, y); UpdateDraggedEntity(pendingPoint, x, y);
} else if(pendingOperation == PENDING_OPERATION_DRAGGING_CONSTRAINT) { } else if(pendingOperation == DRAGGING_CONSTRAINT) {
Constraint *c = SS.constraint.FindById(pendingConstraint); Constraint *c = SS.constraint.FindById(pendingConstraint);
UpdateDraggedPoint(&(c->disp.offset), x, y); UpdateDraggedPoint(&(c->disp.offset), x, y);
} }
} else { } else {
// No buttons pressed. // No buttons pressed.
if(pendingOperation == PENDING_OPERATION_DRAGGING_NEW_POINT) { if(pendingOperation == DRAGGING_NEW_POINT ||
pendingOperation == DRAGGING_NEW_LINE_POINT)
{
UpdateDraggedEntity(pendingPoint, x, y); UpdateDraggedEntity(pendingPoint, x, y);
HitTestMakeSelection(mp);
} else { } else {
// Do our usual hit testing, for the selection. // Do our usual hit testing, for the selection.
Selection s; HitTestMakeSelection(mp);
HitTestMakeSelection(mp, &s);
if(!s.Equals(&hover)) {
hover = s;
InvalidateGraphics();
}
} }
} }
} }
@ -457,18 +458,22 @@ void GraphicsWindow::Selection::Draw(void) {
if(constraint.v) SS.GetConstraint(constraint)->Draw(); if(constraint.v) SS.GetConstraint(constraint)->Draw();
} }
void GraphicsWindow::HitTestMakeSelection(Point2d mp, Selection *dest) { void GraphicsWindow::HitTestMakeSelection(Point2d mp) {
int i; int i;
double d, dmin = 1e12; double d, dmin = 1e12;
Selection s;
memset(dest, 0, sizeof(*dest)); memset(&s, 0, sizeof(s));
// Do the entities // Do the entities
for(i = 0; i < SS.entity.n; i++) { for(i = 0; i < SS.entity.n; i++) {
d = SS.entity.elem[i].GetDistance(mp); Entity *e = &(SS.entity.elem[i]);
// Don't hover whatever's being dragged.
if(e->h.request().v == pendingPoint.request().v) continue;
d = e->GetDistance(mp);
if(d < 10 && d < dmin) { if(d < 10 && d < dmin) {
memset(dest, 0, sizeof(*dest)); memset(&s, 0, sizeof(s));
dest->entity = SS.entity.elem[i].h; s.entity = e->h;
dmin = d; dmin = d;
} }
} }
@ -477,11 +482,16 @@ void GraphicsWindow::HitTestMakeSelection(Point2d mp, Selection *dest) {
for(i = 0; i < SS.constraint.n; i++) { for(i = 0; i < SS.constraint.n; i++) {
d = SS.constraint.elem[i].GetDistance(mp); d = SS.constraint.elem[i].GetDistance(mp);
if(d < 10 && d < dmin) { if(d < 10 && d < dmin) {
memset(dest, 0, sizeof(*dest)); memset(&s, 0, sizeof(s));
dest->constraint = SS.constraint.elem[i].h; s.constraint = SS.constraint.elem[i].h;
dmin = d; dmin = d;
} }
} }
if(!s.Equals(&hover)) {
hover = s;
InvalidateGraphics();
}
} }
void GraphicsWindow::ClearSelection(void) { void GraphicsWindow::ClearSelection(void) {
@ -565,17 +575,41 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) {
hr = AddRequest(Request::LINE_SEGMENT); hr = AddRequest(Request::LINE_SEGMENT);
SS.GetEntity(hr.entity(1))->PointForceTo(v); SS.GetEntity(hr.entity(1))->PointForceTo(v);
pendingOperation = PENDING_OPERATION_DRAGGING_NEW_POINT; pendingOperation = DRAGGING_NEW_LINE_POINT;
pendingPoint = hr.entity(2); pendingPoint = hr.entity(2);
pendingDescription = "click to place next point of line"; pendingDescription = "click to place next point of line";
SS.GetEntity(pendingPoint)->PointForceTo(v); SS.GetEntity(pendingPoint)->PointForceTo(v);
break; break;
case PENDING_OPERATION_DRAGGING_NEW_POINT: case DRAGGING_NEW_POINT:
// The MouseMoved event has already dragged it under the cursor. // The MouseMoved event has already dragged it under the cursor.
pendingOperation = 0; pendingOperation = 0;
pendingPoint.v = 0;
break; 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;
break;
}
// Create a new line segment, so that we continue drawing.
hRequest hr = AddRequest(Request::LINE_SEGMENT);
SS.GetEntity(hr.entity(1))->PointForceTo(v);
// Constrain the line segments to share an endpoint
Constraint::ConstrainCoincident(pendingPoint, 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);
break;
}
case 0: case 0:
default: { default: {
pendingOperation = 0; pendingOperation = 0;
@ -609,8 +643,8 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) {
void GraphicsWindow::MouseLeftUp(double mx, double my) { void GraphicsWindow::MouseLeftUp(double mx, double my) {
switch(pendingOperation) { switch(pendingOperation) {
case PENDING_OPERATION_DRAGGING_POINT: case DRAGGING_POINT:
case PENDING_OPERATION_DRAGGING_CONSTRAINT: case DRAGGING_CONSTRAINT:
pendingOperation = 0; pendingOperation = 0;
pendingPoint.v = 0; pendingPoint.v = 0;
pendingConstraint.v = 0; pendingConstraint.v = 0;

21
polygon.h Normal file
View File

@ -0,0 +1,21 @@
#ifndef __POLYGON_H
#define __POLYGON_H
class SPolyhedron {
public:
};
class SPolygon {
public:
};
class SContour {
public:
};
class SEdgeList {
public:
};
#endif

View File

@ -58,9 +58,13 @@ public:
int solveOrder; int solveOrder;
bool solved; bool solved;
bool visible;
NameStr name; NameStr name;
char *DescriptionString(void); char *DescriptionString(void);
SPolygon GetPolygon(void);
}; };
@ -140,7 +144,9 @@ public:
// Applies only for a CSYS_2D type // Applies only for a CSYS_2D type
void Csys2dGetBasisVectors(Vector *u, Vector *v); void Csys2dGetBasisVectors(Vector *u, Vector *v);
Vector Csys2dGetNormalVector(void);
void Csys2dGetBasisExprs(ExprVector *u, ExprVector *v); void Csys2dGetBasisExprs(ExprVector *u, ExprVector *v);
ExprVector Csys2dGetOffsetExprs(void);
bool IsPoint(void); bool IsPoint(void);
bool IsPointIn3d(void); bool IsPointIn3d(void);
@ -260,6 +266,8 @@ public:
void ModifyToSatisfy(void); void ModifyToSatisfy(void);
void AddEq(IdList<Equation,hEquation> *l, Expr *expr, int index); void AddEq(IdList<Equation,hEquation> *l, Expr *expr, int index);
static Expr *Distance(hEntity pa, hEntity pb); static Expr *Distance(hEntity pa, hEntity pb);
static void ConstrainCoincident(hEntity ptA, hEntity ptB);
}; };
class hEquation { class hEquation {

View File

@ -56,6 +56,7 @@ void MemFree(void *p);
#include "dsc.h" #include "dsc.h"
#include "polygon.h"
#include "sketch.h" #include "sketch.h"
#include "ui.h" #include "ui.h"
#include "expr.h" #include "expr.h"

11
ui.h
View File

@ -101,6 +101,8 @@ public:
MNU_DISTANCE_DIA, MNU_DISTANCE_DIA,
MNU_EQUAL, MNU_EQUAL,
MNU_ON_ENTITY, MNU_ON_ENTITY,
MNU_HORIZONTAL,
MNU_VERTICAL,
MNU_SOLVE_NOW, MNU_SOLVE_NOW,
} MenuId; } MenuId;
typedef void MenuHandler(int id); typedef void MenuHandler(int id);
@ -148,9 +150,10 @@ public:
// Operations that must be completed by doing something with the mouse // Operations that must be completed by doing something with the mouse
// are noted here. // are noted here.
static const int PENDING_OPERATION_DRAGGING_POINT = 0x0f000000; static const int DRAGGING_POINT = 0x0f000000;
static const int PENDING_OPERATION_DRAGGING_NEW_POINT = 0x0f000001; static const int DRAGGING_NEW_POINT = 0x0f000001;
static const int PENDING_OPERATION_DRAGGING_CONSTRAINT = 0x0f000002; static const int DRAGGING_NEW_LINE_POINT = 0x0f000002;
static const int DRAGGING_CONSTRAINT = 0x0f000003;
hEntity pendingPoint; hEntity pendingPoint;
hConstraint pendingConstraint; hConstraint pendingConstraint;
int pendingOperation; int pendingOperation;
@ -175,7 +178,7 @@ public:
Selection hover; Selection hover;
static const int MAX_SELECTED = 32; static const int MAX_SELECTED = 32;
Selection selection[MAX_SELECTED]; Selection selection[MAX_SELECTED];
void HitTestMakeSelection(Point2d mp, Selection *dest); void HitTestMakeSelection(Point2d mp);
void ClearSelection(void); void ClearSelection(void);
struct { struct {
hEntity point[MAX_SELECTED]; hEntity point[MAX_SELECTED];