From 8a0809e6a0e7dd4463ebedcece4e6f27edaf71af Mon Sep 17 00:00:00 2001 From: Jonathan Westhues Date: Fri, 6 Jun 2008 00:14:37 -0800 Subject: [PATCH] Split some large files. [git-p4: depot-paths = "//depot/solvespace/": change = 1777] --- Makefile | 6 +- draw.cpp | 851 ++++++++++++++++++++++++++++++++++++++++ drawentity.cpp | 364 +++++++++++++++++ entity.cpp | 365 +---------------- graphicswin.cpp | 845 --------------------------------------- sketch.cpp => group.cpp | 404 ------------------- groupmesh.cpp | 244 ++++++++++++ request.cpp | 165 ++++++++ 8 files changed, 1632 insertions(+), 1612 deletions(-) create mode 100644 draw.cpp create mode 100644 drawentity.cpp rename sketch.cpp => group.cpp (60%) create mode 100644 groupmesh.cpp create mode 100644 request.cpp diff --git a/Makefile b/Makefile index d2ee419..7b576b6 100644 --- a/Makefile +++ b/Makefile @@ -14,10 +14,14 @@ SSOBJS = $(OBJDIR)\solvespace.obj \ $(OBJDIR)\graphicswin.obj \ $(OBJDIR)\util.obj \ $(OBJDIR)\entity.obj \ - $(OBJDIR)\sketch.obj \ + $(OBJDIR)\drawentity.obj \ + $(OBJDIR)\group.obj \ + $(OBJDIR)\groupmesh.obj \ + $(OBJDIR)\request.obj \ $(OBJDIR)\glhelper.obj \ $(OBJDIR)\expr.obj \ $(OBJDIR)\constraint.obj \ + $(OBJDIR)\draw.obj \ $(OBJDIR)\drawconstraint.obj \ $(OBJDIR)\file.obj \ $(OBJDIR)\undoredo.obj \ diff --git a/draw.cpp b/draw.cpp new file mode 100644 index 0000000..16e809b --- /dev/null +++ b/draw.cpp @@ -0,0 +1,851 @@ +#include "solvespace.h" + +void GraphicsWindow::UpdateDraggedPoint(hEntity hp, double mx, double my) { + Entity *p = SS.GetEntity(hp); + Vector pos = p->PointGetNum(); + UpdateDraggedNum(&pos, mx, my); + p->PointForceTo(pos); +} + +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)); + + orig.mouse.x = mx; + orig.mouse.y = my; + InvalidateGraphics(); +} + +void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, + bool middleDown, bool rightDown, bool shiftDown, bool ctrlDown) +{ + if(GraphicsEditControlIsVisible()) return; + + 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(); + + double dx = (x - orig.mouse.x) / scale; + double dy = (y - orig.mouse.y) / scale; + + // When the view is locked, permit only translation (pan). + if(!(shiftDown || ctrlDown)) { + offset.x = orig.offset.x + dx*projRight.x + dy*projUp.x; + offset.y = orig.offset.y + dx*projRight.y + dy*projUp.y; + offset.z = orig.offset.z + dx*projRight.z + dy*projUp.z; + } else if(ctrlDown) { + double theta = atan2(orig.mouse.y, orig.mouse.x); + theta -= atan2(y, x); + + Vector normal = orig.projRight.Cross(orig.projUp); + projRight = orig.projRight.RotatedAbout(normal, theta); + projUp = orig.projUp.RotatedAbout(normal, theta); + + NormalizeProjectionVectors(); + } else { + 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); + + NormalizeProjectionVectors(); + } + + orig.projRight = projRight; + orig.projUp = projUp; + orig.offset = offset; + orig.mouse.x = x; + orig.mouse.y = y; + + InvalidateGraphics(); + return; + } + + 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; + } + if(pending.operation != 0) { + // We just started a drag, so remember for the undo before + // the drag changes anything. + SS.UndoRemember(); + } + } else { + // Otherwise, just hit test and give up; but don't hit test + // if the mouse is down, because then the user could hover + // a point, mouse down (thus selecting it), and drag, in an + // effort to drag the point, but instead hover a different + // entity before we move far enough to start the drag. + if(!leftDown) HitTestMakeSelection(mp); + } + return; + } + + // 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: { + Entity *p = SS.GetEntity(pending.point); + if((p->type == Entity::POINT_N_ROT_TRANS) && + (shiftDown || ctrlDown)) + { + // These points also come with a rotation, which the user can + // edit by pressing shift or control. + Quaternion q = p->PointGetQuaternion(); + Vector p3 = p->PointGetNum(); + Point2d p2 = ProjectPoint(p3); + + Vector u = q.RotationU(), v = q.RotationV(); + if(ctrlDown) { + double d = mp.DistanceTo(p2); + if(d < 25) { + // Don't start dragging the position about the normal + // until we're a little ways out, to get a reasonable + // reference pos + orig.mouse = mp; + break; + } + double theta = atan2(orig.mouse.y-p2.y, orig.mouse.x-p2.x); + theta -= atan2(y-p2.y, x-p2.x); + + Vector gn = projRight.Cross(projUp); + u = u.RotatedAbout(gn, -theta); + v = v.RotatedAbout(gn, -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(projUp, -s*dx); + u = u.RotatedAbout(projRight, s*dy); + v = v.RotatedAbout(projUp, -s*dx); + v = v.RotatedAbout(projRight, s*dy); + } + q = Quaternion::From(u, v); + p->PointForceQuaternionTo(q); + // Let's rotate about the selected point; so fix up the + // translation so that that point didn't move. + p->PointForceTo(p3); + orig.mouse = mp; + } else { + UpdateDraggedPoint(pending.point, x, y); + HitTestMakeSelection(mp); + } + SS.MarkGroupDirtyByEntity(pending.point); + 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); + + SS.MarkGroupDirtyByEntity(pending.point); + break; + } + case DRAGGING_NEW_ARC_POINT: { + UpdateDraggedPoint(pending.point, x, y); + HitTestMakeSelection(mp); + + hRequest hr = pending.point.request(); + Vector ona = SS.GetEntity(hr.entity(2))->PointGetNum(); + Vector onb = SS.GetEntity(hr.entity(3))->PointGetNum(); + Vector center = (ona.Plus(onb)).ScaledBy(0.5); + + SS.GetEntity(hr.entity(1))->PointForceTo(center); + + SS.MarkGroupDirtyByEntity(pending.point); + 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); + double r = c2.DistanceTo(mp)/scale; + SS.GetEntity(circle->distance)->DistanceForceTo(r); + + SS.MarkGroupDirtyByEntity(pending.circle); + 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 = projRight.Cross(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(projUp, -s*dx); + u = u.RotatedAbout(projRight, s*dy); + v = v.RotatedAbout(projUp, -s*dx); + v = v.RotatedAbout(projRight, s*dy); + } + orig.mouse = mp; + normal->NormalForceTo(Quaternion::From(u, v)); + + SS.MarkGroupDirtyByEntity(pending.normal); + break; + } + + default: oops(); + } + if(pending.operation != 0 && pending.operation != DRAGGING_CONSTRAINT) { + SS.GenerateAll(); + } + havePainted = false; +} + +void GraphicsWindow::ClearPending(void) { + memset(&pending, 0, sizeof(pending)); +} + +void GraphicsWindow::MouseMiddleDown(double x, double y) { + if(GraphicsEditControlIsVisible()) return; + + orig.offset = offset; + orig.projUp = projUp; + orig.projRight = projRight; + orig.mouse.x = x; + orig.mouse.y = y; +} + +hRequest GraphicsWindow::AddRequest(int type) { + return AddRequest(type, true); +} +hRequest GraphicsWindow::AddRequest(int type, bool rememberForUndo) { + if(rememberForUndo) SS.UndoRemember(); + + Request r; + memset(&r, 0, sizeof(r)); + r.group = activeGroup; + Group *g = SS.GetGroup(activeGroup); + if(g->type == Group::DRAWING_3D || g->type == Group::DRAWING_WORKPLANE) { + r.construction = false; + } else { + r.construction = true; + } + r.workplane = ActiveWorkplane(); + r.type = type; + SS.request.AddAndAssignId(&r); + + // 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(-1, -1); + SS.MarkGroupDirty(r.group); + 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->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; + + // Make sure the hover is up to date. + MouseMoved(mx, my, false, false, false, false, false); + orig.mouse.x = mx; + orig.mouse.y = my; + + // The current mouse location + Vector v = offset.ScaledBy(-1); + v = v.Plus(projRight.ScaledBy(mx/scale)); + v = v.Plus(projUp.ScaledBy(my/scale)); + + hRequest hr; + switch(pending.operation) { + case MNU_DATUM_POINT: + hr = AddRequest(Request::DATUM_POINT); + SS.GetEntity(hr.entity(0))->PointForceTo(v); + ConstrainPointByHovered(hr.entity(0)); + + ClearSuper(); + + pending.operation = 0; + break; + + case MNU_LINE_SEGMENT: + hr = AddRequest(Request::LINE_SEGMENT); + SS.GetEntity(hr.entity(1))->PointForceTo(v); + ConstrainPointByHovered(hr.entity(1)); + + ClearSuper(); + + 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_RECTANGLE: { + if(!SS.GW.LockedInWorkplane()) { + Error("Can't draw rectangle in 3d; select a workplane first."); + break; + } + hRequest lns[4]; + int i; + SS.UndoRemember(); + for(i = 0; i < 4; i++) { + lns[i] = AddRequest(Request::LINE_SEGMENT, false); + } + for(i = 0; i < 4; i++) { + Constraint::ConstrainCoincident( + lns[i].entity(1), lns[(i+1)%4].entity(2)); + SS.GetEntity(lns[i].entity(1))->PointForceTo(v); + SS.GetEntity(lns[i].entity(2))->PointForceTo(v); + } + for(i = 0; i < 4; i++) { + Constraint::Constrain( + (i % 2) ? Constraint::HORIZONTAL : Constraint::VERTICAL, + Entity::NO_ENTITY, Entity::NO_ENTITY, + lns[i].entity(0)); + } + ConstrainPointByHovered(lns[2].entity(1)); + + pending.operation = DRAGGING_NEW_POINT; + pending.point = lns[1].entity(2); + pending.description = "click to place other corner of rectangle"; + break; + } + case MNU_CIRCLE: + hr = AddRequest(Request::CIRCLE); + SS.GetEntity(hr.entity(1))->PointForceTo(v); + SS.GetEntity(hr.entity(32))->NormalForceTo( + Quaternion::From(SS.GW.projRight, SS.GW.projUp)); + ConstrainPointByHovered(hr.entity(1)); + + ClearSuper(); + + 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_ARC: + if(!SS.GW.LockedInWorkplane()) { + Error("Can't draw arc in 3d; select a workplane first."); + ClearPending(); + break; + } + hr = AddRequest(Request::ARC_OF_CIRCLE); + SS.GetEntity(hr.entity(1))->PointForceTo(v); + SS.GetEntity(hr.entity(2))->PointForceTo(v); + SS.GetEntity(hr.entity(3))->PointForceTo(v); + ConstrainPointByHovered(hr.entity(2)); + + ClearSuper(); + + pending.operation = DRAGGING_NEW_ARC_POINT; + pending.point = hr.entity(3); + pending.description = "click to place point"; + break; + + case MNU_CUBIC: + hr = AddRequest(Request::CUBIC); + SS.GetEntity(hr.entity(1))->PointForceTo(v); + SS.GetEntity(hr.entity(2))->PointForceTo(v); + SS.GetEntity(hr.entity(3))->PointForceTo(v); + SS.GetEntity(hr.entity(4))->PointForceTo(v); + ConstrainPointByHovered(hr.entity(1)); + + ClearSuper(); + + pending.operation = DRAGGING_NEW_CUBIC_POINT; + pending.point = hr.entity(4); + pending.description = "click to place next point of cubic"; + break; + + case MNU_WORKPLANE: + hr = AddRequest(Request::WORKPLANE); + SS.GetEntity(hr.entity(1))->PointForceTo(v); + SS.GetEntity(hr.entity(32))->NormalForceTo( + Quaternion::From(SS.GW.projRight, SS.GW.projUp)); + ConstrainPointByHovered(hr.entity(1)); + + ClearSuper(); + break; + + case DRAGGING_RADIUS: + case DRAGGING_NEW_POINT: + // The MouseMoved event has already dragged it as desired. + ClearPending(); + break; + + case DRAGGING_NEW_ARC_POINT: + case DRAGGING_NEW_CUBIC_POINT: + ConstrainPointByHovered(pending.point); + ClearPending(); + break; + + case DRAGGING_NEW_LINE_POINT: { + if(ConstrainPointByHovered(pending.point)) { + ClearPending(); + break; + } + // Create a new line segment, so that we continue drawing. + hRequest hr = AddRequest(Request::LINE_SEGMENT); + SS.GetEntity(hr.entity(1))->PointForceTo(v); + SS.GetEntity(hr.entity(2))->PointForceTo(v); + + // Constrain the line segments to share an endpoint + Constraint::ConstrainCoincident(pending.point, hr.entity(1)); + + // And drag an endpoint of the new line segment + pending.operation = DRAGGING_NEW_LINE_POINT; + pending.point = hr.entity(2); + pending.description = "click to place next point of next line"; + + break; + } + + case 0: + default: { + ClearPending(); + + if(hover.IsEmpty()) break; + + // If an item is hovered, then by clicking on it, we toggle its + // selection state. + int i; + for(i = 0; i < MAX_SELECTED; i++) { + if(selection[i].Equals(&hover)) { + selection[i].Clear(); + break; + } + } + if(i != MAX_SELECTED) break; + + if(hover.entity.v != 0 && SS.GetEntity(hover.entity)->IsFace()) { + // In the interest of speed for the triangle drawing code, + // only two faces may be selected at a time. + int c = 0; + for(i = 0; i < MAX_SELECTED; i++) { + hEntity he = selection[i].entity; + if(he.v != 0 && SS.GetEntity(he)->IsFace()) { + c++; + if(c >= 2) selection[i].Clear(); + } + } + } + + for(i = 0; i < MAX_SELECTED; i++) { + if(selection[i].IsEmpty()) { + selection[i] = hover; + break; + } + } + break; + } + } + + SS.later.showTW = true; + InvalidateGraphics(); +} + +void GraphicsWindow::MouseLeftUp(double mx, double my) { + switch(pending.operation) { + case DRAGGING_POINT: + case DRAGGING_CONSTRAINT: + case DRAGGING_NORMAL: + case DRAGGING_RADIUS: + ClearPending(); + break; + + default: + break; // do nothing + } +} + +void GraphicsWindow::MouseLeftDoubleClick(double mx, double my) { + if(GraphicsEditControlIsVisible()) return; + + if(hover.constraint.v) { + constraintBeingEdited = hover.constraint; + ClearSuper(); + + Constraint *c = SS.GetConstraint(constraintBeingEdited); + Vector p3 = c->GetLabelPos(); + Point2d p2 = ProjectPoint(p3); + ShowGraphicsEditControl((int)p2.x, (int)p2.y, c->exprA->Print()); + } +} + +void GraphicsWindow::EditControlDone(char *s) { + Expr *e = Expr::From(s); + if(e) { + SS.UndoRemember(); + + Constraint *c = SS.GetConstraint(constraintBeingEdited); + Expr::FreeKeep(&(c->exprA)); + c->exprA = e->DeepCopyKeep(); + + HideGraphicsEditControl(); + SS.MarkGroupDirty(c->group); + SS.GenerateAll(); + } else { + Error("Not a valid number or expression: '%s'", s); + } +} + +void GraphicsWindow::MouseScroll(double x, double y, int delta) { + double offsetRight = offset.Dot(projRight); + double offsetUp = offset.Dot(projUp); + + double righti = x/scale - offsetRight; + double upi = y/scale - offsetUp; + + if(delta > 0) { + scale *= 1.2; + } else { + scale /= 1.2; + } + + double rightf = x/scale - offsetRight; + double upf = y/scale - offsetUp; + + offset = offset.Plus(projRight.ScaledBy(rightf - righti)); + offset = offset.Plus(projUp.ScaledBy(upf - upi)); + + InvalidateGraphics(); +} + +bool GraphicsWindow::Selection::Equals(Selection *b) { + if(entity.v != b->entity.v) return false; + if(constraint.v != b->constraint.v) return false; + return true; +} + +bool GraphicsWindow::Selection::IsEmpty(void) { + if(entity.v) return false; + if(constraint.v) return false; + return true; +} + +void GraphicsWindow::Selection::Clear(void) { + entity.v = constraint.v = 0; + emphasized = false; +} + +void GraphicsWindow::Selection::Draw(void) { + Vector refp; + if(entity.v) { + glLineWidth(1.5); + Entity *e = SS.GetEntity(entity); + e->Draw(); + if(emphasized) refp = e->GetReferencePos(); + glLineWidth(1); + } + if(constraint.v) { + Constraint *c = SS.GetConstraint(constraint); + c->Draw(); + if(emphasized) refp = c->GetReferencePos(); + } + if(emphasized && (constraint.v || entity.v)) { + double s = 0.501/SS.GW.scale; + Vector topLeft = SS.GW.projRight.ScaledBy(-SS.GW.width*s); + topLeft = topLeft.Plus(SS.GW.projUp.ScaledBy(SS.GW.height*s)); + topLeft = topLeft.Minus(SS.GW.offset); + + glLineWidth(40); + glColor4d(1.0, 1.0, 0, 0.2); + glBegin(GL_LINES); + glxVertex3v(topLeft); + glxVertex3v(refp); + glEnd(); + glLineWidth(1); + } +} + +void GraphicsWindow::ClearSelection(void) { + for(int i = 0; i < MAX_SELECTED; i++) { + selection[i].Clear(); + } + SS.later.showTW = true; + 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; + for(i = 0; i < MAX_SELECTED; i++) { + Selection *s = &(selection[i]); + if(s->entity.v) { + (gs.n)++; + + Entity *e = SS.entity.FindById(s->entity); + // A list of points, and a list of all entities that aren't points. + if(e->IsPoint()) { + gs.point[(gs.points)++] = s->entity; + } else { + gs.entity[(gs.entities)++] = s->entity; + } + + // And an auxiliary list of normals, including normals from + // workplanes. + if(e->IsNormal()) { + gs.anyNormal[(gs.anyNormals)++] = s->entity; + } else if(e->IsWorkplane()) { + gs.anyNormal[(gs.anyNormals)++] = e->Normal()->h; + } + + // And of vectors (i.e., stuff with a direction to constrain) + if(e->HasVector()) { + gs.vector[(gs.vectors)++] = s->entity; + } + + // Faces (which are special, associated/drawn with triangles) + if(e->IsFace()) { + gs.face[(gs.faces)++] = s->entity; + } + + // And some aux counts too + switch(e->type) { + case Entity::WORKPLANE: (gs.workplanes)++; break; + case Entity::LINE_SEGMENT: (gs.lineSegments)++; break; + + case Entity::ARC_OF_CIRCLE: + case Entity::CIRCLE: (gs.circlesOrArcs)++; break; + } + } + if(s->constraint.v) { + gs.constraint[(gs.constraints)++] = s->constraint; + } + } +} + + +void GraphicsWindow::HitTestMakeSelection(Point2d mp) { + int i; + double d, dmin = 1e12; + Selection s; + ZERO(&s); + + // Do the entities + 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 == pending.point.request().v) continue; + + d = e->GetDistance(mp); + if(d < 10 && d < dmin) { + memset(&s, 0, sizeof(s)); + s.entity = e->h; + dmin = d; + } + } + + // Constraints + for(i = 0; i < SS.constraint.n; i++) { + d = SS.constraint.elem[i].GetDistance(mp); + if(d < 10 && d < dmin) { + memset(&s, 0, sizeof(s)); + s.constraint = SS.constraint.elem[i].h; + dmin = d; + } + } + + // Faces, from the triangle mesh; these are lowest priority + if(s.constraint.v == 0 && s.entity.v == 0 && showShaded && showFaces) { + SMesh *m = &((SS.GetGroup(activeGroup))->mesh); + DWORD v = m->FirstIntersectionWith(mp); + if(v) { + s.entity.v = v; + } + } + + if(!s.Equals(&hover)) { + hover = s; + InvalidateGraphics(); + } +} + +Vector GraphicsWindow::VectorFromProjs(double right, double up, double fwd) { + Vector n = projRight.Cross(projUp); + Vector r = offset.ScaledBy(-1); + r = r.Plus(projRight.ScaledBy(right)); + r = r.Plus(projUp.ScaledBy(up)); + r = r.Plus(n.ScaledBy(fwd)); + return r; +} + +void GraphicsWindow::Paint(int w, int h) { + havePainted = true; + width = w; height = h; + + glViewport(0, 0, w, h); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + glScaled(scale*2.0/w, scale*2.0/h, scale*1.0/30000); + + double tx = projRight.Dot(offset); + double ty = projUp.Dot(offset); + Vector n = projUp.Cross(projRight); + double tz = n.Dot(offset); + double mat[16]; + MakeMatrix(mat, projRight.x, projRight.y, projRight.z, tx, + projUp.x, projUp.y, projUp.z, ty, + n.x, n.y, n.z, tz, + 0, 0, 0, 1); + glMultMatrixd(mat); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glShadeModel(GL_SMOOTH); + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_BLEND); + glEnable(GL_LINE_SMOOTH); + glEnable(GL_POLYGON_OFFSET_LINE); + glEnable(GL_POLYGON_OFFSET_FILL); + glEnable(GL_DEPTH_TEST); + glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); + glEnable(GL_NORMALIZE); + + // At the same depth, we want later lines drawn over earlier. + glDepthFunc(GL_LEQUAL); + + glClearDepth(1.0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + Vector light = VectorFromProjs(-0.49*w/scale, 0.49*h/scale, 0); + GLfloat lightPos[4] = + { (GLfloat)light.x, (GLfloat)light.y, (GLfloat)light.z, 0 }; + glLightfv(GL_LIGHT0, GL_POSITION, lightPos); + glEnable(GL_LIGHT0); + + glLightModelf(GL_LIGHT_MODEL_TWO_SIDE, 1); + GLfloat ambient[4] = { 0.4f, 0.4f, 0.4f, 1.0f }; + glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient); + + glxUnlockColor(); + + int i; + // Draw the groups; this fills the polygons in a drawing group, and + // draws the solid mesh. + (SS.GetGroup(activeGroup))->Draw(); + + // Now draw the entities + if(showHdnLines) glDisable(GL_DEPTH_TEST); + Entity::DrawAll(); + + glDisable(GL_DEPTH_TEST); + // Draw the constraints + for(i = 0; i < SS.constraint.n; i++) { + SS.constraint.elem[i].Draw(); + } + + // Then redraw whatever the mouse is hovering over, highlighted. + glDisable(GL_DEPTH_TEST); + glxLockColorTo(1, 1, 0); + hover.Draw(); + + // And finally draw the selection, same mechanism. + glxLockColorTo(1, 0, 0); + for(i = 0; i < MAX_SELECTED; i++) { + selection[i].Draw(); + } +} + diff --git a/drawentity.cpp b/drawentity.cpp new file mode 100644 index 0000000..c984dfb --- /dev/null +++ b/drawentity.cpp @@ -0,0 +1,364 @@ +#include "solvespace.h" + +void Entity::LineDrawOrGetDistance(Vector a, Vector b) { + if(dogd.drawing) { + // glPolygonOffset works only on polys, not lines, so do it myself + Vector adj = SS.GW.projRight.Cross(SS.GW.projUp); + adj = adj.ScaledBy(5/SS.GW.scale); + glBegin(GL_LINES); + glxVertex3v(a.Plus(adj)); + glxVertex3v(b.Plus(adj)); + glEnd(); + } else { + Point2d ap = SS.GW.ProjectPoint(a); + Point2d bp = SS.GW.ProjectPoint(b); + + double d = dogd.mp.DistanceToLine(ap, bp.Minus(ap), true); + dogd.dmin = min(dogd.dmin, d); + } + dogd.refp = (a.Plus(b)).ScaledBy(0.5); +} + +void Entity::LineDrawOrGetDistanceOrEdge(Vector a, Vector b) { + LineDrawOrGetDistance(a, b); + if(dogd.edges && !construction) { + dogd.edges->AddEdge(a, b); + } +} + +void Entity::DrawAll(void) { + // This handles points and line segments as a special case, because I + // seem to be able to get a huge speedup that way, by consolidating + // stuff to gl. + int i; + if(SS.GW.showPoints) { + double s = 3.5/SS.GW.scale; + Vector r = SS.GW.projRight.ScaledBy(s); + Vector d = SS.GW.projUp.ScaledBy(s); + glxColor3d(0, 0.8, 0); + glPolygonOffset(-10, -10); + glBegin(GL_QUADS); + for(i = 0; i < SS.entity.n; i++) { + Entity *e = &(SS.entity.elem[i]); + if(!e->IsPoint()) continue; + if(!(SS.GetGroup(e->group)->visible)) continue; + + Vector v = e->PointGetNum(); + glxVertex3v(v.Plus (r).Plus (d)); + glxVertex3v(v.Plus (r).Minus(d)); + glxVertex3v(v.Minus(r).Minus(d)); + glxVertex3v(v.Minus(r).Plus (d)); + } + glEnd(); + glPolygonOffset(0, 0); + } + + glLineWidth(1.5); + for(i = 0; i < SS.entity.n; i++) { + Entity *e = &(SS.entity.elem[i]); + if(e->IsPoint()) + { + continue; // already handled + } + e->Draw(); + } + glLineWidth(1); +} + +void Entity::Draw(void) { + dogd.drawing = true; + dogd.edges = NULL; + DrawOrGetDistance(); +} + +void Entity::GenerateEdges(SEdgeList *el) { + dogd.drawing = false; + dogd.edges = el; + DrawOrGetDistance(); + dogd.edges = NULL; +} + +double Entity::GetDistance(Point2d mp) { + dogd.drawing = false; + dogd.edges = NULL; + dogd.mp = mp; + dogd.dmin = 1e12; + + DrawOrGetDistance(); + + return dogd.dmin; +} + +Vector Entity::GetReferencePos(void) { + dogd.drawing = false; + dogd.edges = NULL; + + dogd.refp = SS.GW.offset.ScaledBy(-1); + DrawOrGetDistance(); + + return dogd.refp; +} + +bool Entity::IsVisible(void) { + Group *g = SS.GetGroup(group); + + if(g->h.v == Group::HGROUP_REFERENCES.v && IsNormal()) { + // The reference normals are always shown + return true; + } + if(!g->visible) return false; + if(SS.GroupsInOrder(SS.GW.activeGroup, group)) return false; + + if(IsPoint() && !SS.GW.showPoints) return false; + if(IsWorkplane() && !SS.GW.showWorkplanes) return false; + if(IsNormal() && !SS.GW.showNormals) return false; + + if(IsWorkplane() && !h.isFromRequest()) { + // The group-associated workplanes are hidden outside their group. + if(g->h.v != SS.GW.activeGroup.v) return false; + } + return true; +} + +void Entity::DrawOrGetDistance(void) { + // If an entity is invisible, then it doesn't get shown, and it doesn't + // contribute a distance for the selection, but it still generates edges. + if(!dogd.edges) { + if(!IsVisible()) return; + } + + Group *g = SS.GetGroup(group); + + if(group.v != SS.GW.activeGroup.v) { + glxColor3d(0.5, 0.3, 0.0); + } else if(construction) { + glxColor3d(0.1, 0.7, 0.1); + } else { + glxColor3d(1, 1, 1); + } + + switch(type) { + case POINT_N_COPY: + case POINT_N_TRANS: + case POINT_N_ROT_TRANS: + case POINT_N_ROT_AA: + case POINT_IN_3D: + case POINT_IN_2D: { + Vector v = PointGetNum(); + + if(dogd.drawing) { + double s = 3.5; + Vector r = SS.GW.projRight.ScaledBy(s/SS.GW.scale); + Vector d = SS.GW.projUp.ScaledBy(s/SS.GW.scale); + + glxColor3d(0, 0.8, 0); + glPolygonOffset(-10, -10); + glBegin(GL_QUADS); + glxVertex3v(v.Plus (r).Plus (d)); + glxVertex3v(v.Plus (r).Minus(d)); + glxVertex3v(v.Minus(r).Minus(d)); + glxVertex3v(v.Minus(r).Plus (d)); + glEnd(); + glPolygonOffset(0, 0); + } else { + Point2d pp = SS.GW.ProjectPoint(v); + // Make a free point slightly easier to select, so that with + // coincident points, we select the free one. + dogd.dmin = pp.DistanceTo(dogd.mp) - 4; + } + break; + } + + case NORMAL_N_COPY: + case NORMAL_N_ROT: + case NORMAL_N_ROT_AA: + case NORMAL_IN_3D: + case NORMAL_IN_2D: { + int i; + for(i = 0; i < 2; i++) { + hRequest hr = h.request(); + double f = (i == 0 ? 0.4 : 1); + 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); + if(i > 0) break; + } + + Quaternion q = NormalGetNum(); + Vector tail; + if(i == 0) { + tail = SS.GetEntity(point[0])->PointGetNum(); + glLineWidth(1); + } else { + // Draw an extra copy of the x, y, and z axes, that's + // always in the corner of the view and at the front. + // So those are always available, perhaps useful. + double s = SS.GW.scale; + double h = 60 - SS.GW.height/2; + double w = 60 - SS.GW.width/2; + Vector gn = SS.GW.projRight.Cross(SS.GW.projUp); + tail = SS.GW.projRight.ScaledBy(w/s).Plus( + SS.GW.projUp. ScaledBy(h/s)).Plus( + gn.ScaledBy(-4*w/s)).Minus(SS.GW.offset); + glLineWidth(2); + } + + Vector v = (q.RotationN()).WithMagnitude(50/SS.GW.scale); + Vector tip = tail.Plus(v); + LineDrawOrGetDistance(tail, tip); + + v = v.WithMagnitude(12/SS.GW.scale); + Vector axis = q.RotationV(); + LineDrawOrGetDistance(tip,tip.Minus(v.RotatedAbout(axis, 0.6))); + LineDrawOrGetDistance(tip,tip.Minus(v.RotatedAbout(axis,-0.6))); + } + glLineWidth(1.5); + break; + } + + case DISTANCE: + case DISTANCE_N_COPY: + // These are used only as data structures, nothing to display. + break; + + case WORKPLANE: { + Vector p; + p = SS.GetEntity(point[0])->PointGetNum(); + + Vector u = Normal()->NormalU(); + Vector v = Normal()->NormalV(); + + double s = (min(SS.GW.width, SS.GW.height))*0.45/SS.GW.scale; + + Vector us = u.ScaledBy(s); + Vector vs = v.ScaledBy(s); + + Vector pp = p.Plus (us).Plus (vs); + Vector pm = p.Plus (us).Minus(vs); + Vector mm = p.Minus(us).Minus(vs), mm2 = mm; + Vector mp = p.Minus(us).Plus (vs); + + glLineWidth(1); + glxColor3d(0, 0.3, 0.3); + glEnable(GL_LINE_STIPPLE); + glLineStipple(3, 0x1111); + if(!h.isFromRequest()) { + mm = mm.Plus(v.ScaledBy(60/SS.GW.scale)); + mm2 = mm2.Plus(u.ScaledBy(60/SS.GW.scale)); + LineDrawOrGetDistance(mm2, mm); + } + LineDrawOrGetDistance(pp, pm); + LineDrawOrGetDistance(pm, mm2); + LineDrawOrGetDistance(mm, mp); + LineDrawOrGetDistance(mp, pp); + glDisable(GL_LINE_STIPPLE); + glLineWidth(1.5); + + char *str = DescriptionString()+5; + if(dogd.drawing) { + glPushMatrix(); + glxTranslatev(mm2); + glxOntoWorkplane(u, v); + glxWriteText(str); + glPopMatrix(); + } else { + Vector pos = mm2.Plus(u.ScaledBy(glxStrWidth(str)/2)).Plus( + v.ScaledBy(glxStrHeight()/2)); + Point2d pp = SS.GW.ProjectPoint(pos); + dogd.dmin = min(dogd.dmin, pp.DistanceTo(dogd.mp) - 10); + // If a line lies in a plane, then select the line, not + // the plane. + dogd.dmin += 3; + } + break; + } + + case LINE_SEGMENT: { + 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])->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++) { + double t = ((double)i)/n; + Vector p = + (p0.ScaledBy((1 - t)*(1 - t)*(1 - t))).Plus( + (p1.ScaledBy(3*t*(1 - t)*(1 - t))).Plus( + (p2.ScaledBy(3*t*t*(1 - t))).Plus( + (p3.ScaledBy(t*t*t))))); + LineDrawOrGetDistanceOrEdge(prev, p); + prev = p; + } + break; + } + +#define CIRCLE_SIDES(r) (7 + (int)(sqrt(r*SS.GW.scale))) + case ARC_OF_CIRCLE: { + Vector c = SS.GetEntity(point[0])->PointGetNum(); + Vector pa = SS.GetEntity(point[1])->PointGetNum(); + Vector pb = SS.GetEntity(point[2])->PointGetNum(); + Quaternion q = SS.GetEntity(normal)->NormalGetNum(); + Vector u = q.RotationU(), v = q.RotationV(); + + double ra = (pa.Minus(c)).Magnitude(); + double rb = (pb.Minus(c)).Magnitude(); + + double thetaa, thetab, dtheta; + ArcGetAngles(&thetaa, &thetab, &dtheta); + + int i, n = 3 + (int)(CIRCLE_SIDES(ra)*dtheta/(2*PI)); + Vector prev = pa; + for(i = 1; i <= n; i++) { + double theta = thetaa + (dtheta*i)/n; + double r = ra + ((rb - ra)*i)/n; + Vector d = u.ScaledBy(cos(theta)).Plus(v.ScaledBy(sin(theta))); + Vector p = c.Plus(d.ScaledBy(r)); + LineDrawOrGetDistanceOrEdge(prev, p); + prev = p; + } + break; + } + + case CIRCLE: { + Quaternion q = SS.GetEntity(normal)->NormalGetNum(); + double r = SS.GetEntity(distance)->DistanceGetNum(); + Vector center = SS.GetEntity(point[0])->PointGetNum(); + Vector u = q.RotationU(), v = q.RotationV(); + + int i, c = CIRCLE_SIDES(r); + Vector prev = u.ScaledBy(r).Plus(center); + for(i = 1; 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; + } + + case FACE_NORMAL_PT: + case FACE_XPROD: + case FACE_N_ROT_TRANS: + // Do nothing; these are drawn with the triangle mesh + break; + + default: + oops(); + } +} + diff --git a/entity.cpp b/entity.cpp index b381430..0af5511 100644 --- a/entity.cpp +++ b/entity.cpp @@ -1,5 +1,8 @@ #include "solvespace.h" +const hEntity Entity::FREE_IN_3D = { 0 }; +const hEntity Entity::NO_ENTITY = { 0 }; + char *Entity::DescriptionString(void) { if(h.isFromRequest()) { Request *r = SS.GetRequest(h.request()); @@ -616,368 +619,6 @@ ExprVector Entity::FaceGetPointExprs(void) { return r; } -void Entity::LineDrawOrGetDistance(Vector a, Vector b) { - if(dogd.drawing) { - // glPolygonOffset works only on polys, not lines, so do it myself - Vector adj = SS.GW.projRight.Cross(SS.GW.projUp); - adj = adj.ScaledBy(5/SS.GW.scale); - glBegin(GL_LINES); - glxVertex3v(a.Plus(adj)); - glxVertex3v(b.Plus(adj)); - glEnd(); - } else { - Point2d ap = SS.GW.ProjectPoint(a); - Point2d bp = SS.GW.ProjectPoint(b); - - double d = dogd.mp.DistanceToLine(ap, bp.Minus(ap), true); - dogd.dmin = min(dogd.dmin, d); - } - dogd.refp = (a.Plus(b)).ScaledBy(0.5); -} - -void Entity::LineDrawOrGetDistanceOrEdge(Vector a, Vector b) { - LineDrawOrGetDistance(a, b); - if(dogd.edges && !construction) { - dogd.edges->AddEdge(a, b); - } -} - -void Entity::DrawAll(void) { - // This handles points and line segments as a special case, because I - // seem to be able to get a huge speedup that way, by consolidating - // stuff to gl. - int i; - if(SS.GW.showPoints) { - double s = 3.5/SS.GW.scale; - Vector r = SS.GW.projRight.ScaledBy(s); - Vector d = SS.GW.projUp.ScaledBy(s); - glxColor3d(0, 0.8, 0); - glPolygonOffset(-10, -10); - glBegin(GL_QUADS); - for(i = 0; i < SS.entity.n; i++) { - Entity *e = &(SS.entity.elem[i]); - if(!e->IsPoint()) continue; - if(!(SS.GetGroup(e->group)->visible)) continue; - - Vector v = e->PointGetNum(); - glxVertex3v(v.Plus (r).Plus (d)); - glxVertex3v(v.Plus (r).Minus(d)); - glxVertex3v(v.Minus(r).Minus(d)); - glxVertex3v(v.Minus(r).Plus (d)); - } - glEnd(); - glPolygonOffset(0, 0); - } - - glLineWidth(1.5); - for(i = 0; i < SS.entity.n; i++) { - Entity *e = &(SS.entity.elem[i]); - if(e->IsPoint()) - { - continue; // already handled - } - e->Draw(); - } - glLineWidth(1); -} - -void Entity::Draw(void) { - dogd.drawing = true; - dogd.edges = NULL; - DrawOrGetDistance(); -} - -void Entity::GenerateEdges(SEdgeList *el) { - dogd.drawing = false; - dogd.edges = el; - DrawOrGetDistance(); - dogd.edges = NULL; -} - -double Entity::GetDistance(Point2d mp) { - dogd.drawing = false; - dogd.edges = NULL; - dogd.mp = mp; - dogd.dmin = 1e12; - - DrawOrGetDistance(); - - return dogd.dmin; -} - -Vector Entity::GetReferencePos(void) { - dogd.drawing = false; - dogd.edges = NULL; - - dogd.refp = SS.GW.offset.ScaledBy(-1); - DrawOrGetDistance(); - - return dogd.refp; -} - -bool Entity::IsVisible(void) { - Group *g = SS.GetGroup(group); - - if(g->h.v == Group::HGROUP_REFERENCES.v && IsNormal()) { - // The reference normals are always shown - return true; - } - if(!g->visible) return false; - if(SS.GroupsInOrder(SS.GW.activeGroup, group)) return false; - - if(IsPoint() && !SS.GW.showPoints) return false; - if(IsWorkplane() && !SS.GW.showWorkplanes) return false; - if(IsNormal() && !SS.GW.showNormals) return false; - - if(IsWorkplane() && !h.isFromRequest()) { - // The group-associated workplanes are hidden outside their group. - if(g->h.v != SS.GW.activeGroup.v) return false; - } - return true; -} - -void Entity::DrawOrGetDistance(void) { - // If an entity is invisible, then it doesn't get shown, and it doesn't - // contribute a distance for the selection, but it still generates edges. - if(!dogd.edges) { - if(!IsVisible()) return; - } - - Group *g = SS.GetGroup(group); - - if(group.v != SS.GW.activeGroup.v) { - glxColor3d(0.5, 0.3, 0.0); - } else if(construction) { - glxColor3d(0.1, 0.7, 0.1); - } else { - glxColor3d(1, 1, 1); - } - - switch(type) { - case POINT_N_COPY: - case POINT_N_TRANS: - case POINT_N_ROT_TRANS: - case POINT_N_ROT_AA: - case POINT_IN_3D: - case POINT_IN_2D: { - Vector v = PointGetNum(); - - if(dogd.drawing) { - double s = 3.5; - Vector r = SS.GW.projRight.ScaledBy(s/SS.GW.scale); - Vector d = SS.GW.projUp.ScaledBy(s/SS.GW.scale); - - glxColor3d(0, 0.8, 0); - glPolygonOffset(-10, -10); - glBegin(GL_QUADS); - glxVertex3v(v.Plus (r).Plus (d)); - glxVertex3v(v.Plus (r).Minus(d)); - glxVertex3v(v.Minus(r).Minus(d)); - glxVertex3v(v.Minus(r).Plus (d)); - glEnd(); - glPolygonOffset(0, 0); - } else { - Point2d pp = SS.GW.ProjectPoint(v); - // Make a free point slightly easier to select, so that with - // coincident points, we select the free one. - dogd.dmin = pp.DistanceTo(dogd.mp) - 4; - } - break; - } - - case NORMAL_N_COPY: - case NORMAL_N_ROT: - case NORMAL_N_ROT_AA: - case NORMAL_IN_3D: - case NORMAL_IN_2D: { - int i; - for(i = 0; i < 2; i++) { - hRequest hr = h.request(); - double f = (i == 0 ? 0.4 : 1); - 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); - if(i > 0) break; - } - - Quaternion q = NormalGetNum(); - Vector tail; - if(i == 0) { - tail = SS.GetEntity(point[0])->PointGetNum(); - glLineWidth(1); - } else { - // Draw an extra copy of the x, y, and z axes, that's - // always in the corner of the view and at the front. - // So those are always available, perhaps useful. - double s = SS.GW.scale; - double h = 60 - SS.GW.height/2; - double w = 60 - SS.GW.width/2; - Vector gn = SS.GW.projRight.Cross(SS.GW.projUp); - tail = SS.GW.projRight.ScaledBy(w/s).Plus( - SS.GW.projUp. ScaledBy(h/s)).Plus( - gn.ScaledBy(-4*w/s)).Minus(SS.GW.offset); - glLineWidth(2); - } - - Vector v = (q.RotationN()).WithMagnitude(50/SS.GW.scale); - Vector tip = tail.Plus(v); - LineDrawOrGetDistance(tail, tip); - - v = v.WithMagnitude(12/SS.GW.scale); - Vector axis = q.RotationV(); - LineDrawOrGetDistance(tip,tip.Minus(v.RotatedAbout(axis, 0.6))); - LineDrawOrGetDistance(tip,tip.Minus(v.RotatedAbout(axis,-0.6))); - } - glLineWidth(1.5); - break; - } - - case DISTANCE: - case DISTANCE_N_COPY: - // These are used only as data structures, nothing to display. - break; - - case WORKPLANE: { - Vector p; - p = SS.GetEntity(point[0])->PointGetNum(); - - Vector u = Normal()->NormalU(); - Vector v = Normal()->NormalV(); - - double s = (min(SS.GW.width, SS.GW.height))*0.45/SS.GW.scale; - - Vector us = u.ScaledBy(s); - Vector vs = v.ScaledBy(s); - - Vector pp = p.Plus (us).Plus (vs); - Vector pm = p.Plus (us).Minus(vs); - Vector mm = p.Minus(us).Minus(vs), mm2 = mm; - Vector mp = p.Minus(us).Plus (vs); - - glLineWidth(1); - glxColor3d(0, 0.3, 0.3); - glEnable(GL_LINE_STIPPLE); - glLineStipple(3, 0x1111); - if(!h.isFromRequest()) { - mm = mm.Plus(v.ScaledBy(60/SS.GW.scale)); - mm2 = mm2.Plus(u.ScaledBy(60/SS.GW.scale)); - LineDrawOrGetDistance(mm2, mm); - } - LineDrawOrGetDistance(pp, pm); - LineDrawOrGetDistance(pm, mm2); - LineDrawOrGetDistance(mm, mp); - LineDrawOrGetDistance(mp, pp); - glDisable(GL_LINE_STIPPLE); - glLineWidth(1.5); - - char *str = DescriptionString()+5; - if(dogd.drawing) { - glPushMatrix(); - glxTranslatev(mm2); - glxOntoWorkplane(u, v); - glxWriteText(str); - glPopMatrix(); - } else { - Vector pos = mm2.Plus(u.ScaledBy(glxStrWidth(str)/2)).Plus( - v.ScaledBy(glxStrHeight()/2)); - Point2d pp = SS.GW.ProjectPoint(pos); - dogd.dmin = min(dogd.dmin, pp.DistanceTo(dogd.mp) - 10); - // If a line lies in a plane, then select the line, not - // the plane. - dogd.dmin += 3; - } - break; - } - - case LINE_SEGMENT: { - 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])->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++) { - double t = ((double)i)/n; - Vector p = - (p0.ScaledBy((1 - t)*(1 - t)*(1 - t))).Plus( - (p1.ScaledBy(3*t*(1 - t)*(1 - t))).Plus( - (p2.ScaledBy(3*t*t*(1 - t))).Plus( - (p3.ScaledBy(t*t*t))))); - LineDrawOrGetDistanceOrEdge(prev, p); - prev = p; - } - break; - } - -#define CIRCLE_SIDES(r) (7 + (int)(sqrt(r*SS.GW.scale))) - case ARC_OF_CIRCLE: { - Vector c = SS.GetEntity(point[0])->PointGetNum(); - Vector pa = SS.GetEntity(point[1])->PointGetNum(); - Vector pb = SS.GetEntity(point[2])->PointGetNum(); - Quaternion q = SS.GetEntity(normal)->NormalGetNum(); - Vector u = q.RotationU(), v = q.RotationV(); - - double ra = (pa.Minus(c)).Magnitude(); - double rb = (pb.Minus(c)).Magnitude(); - - double thetaa, thetab, dtheta; - ArcGetAngles(&thetaa, &thetab, &dtheta); - - int i, n = 3 + (int)(CIRCLE_SIDES(ra)*dtheta/(2*PI)); - Vector prev = pa; - for(i = 1; i <= n; i++) { - double theta = thetaa + (dtheta*i)/n; - double r = ra + ((rb - ra)*i)/n; - Vector d = u.ScaledBy(cos(theta)).Plus(v.ScaledBy(sin(theta))); - Vector p = c.Plus(d.ScaledBy(r)); - LineDrawOrGetDistanceOrEdge(prev, p); - prev = p; - } - break; - } - - case CIRCLE: { - Quaternion q = SS.GetEntity(normal)->NormalGetNum(); - double r = SS.GetEntity(distance)->DistanceGetNum(); - Vector center = SS.GetEntity(point[0])->PointGetNum(); - Vector u = q.RotationU(), v = q.RotationV(); - - int i, c = CIRCLE_SIDES(r); - Vector prev = u.ScaledBy(r).Plus(center); - for(i = 1; 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; - } - - case FACE_NORMAL_PT: - case FACE_XPROD: - case FACE_N_ROT_TRANS: - // Do nothing; these are drawn with the triangle mesh - break; - - default: - oops(); - } -} - void Entity::AddEq(IdList *l, Expr *expr, int index) { Equation eq; eq.e = expr; diff --git a/graphicswin.cpp b/graphicswin.cpp index eaca54b..f11dc00 100644 --- a/graphicswin.cpp +++ b/graphicswin.cpp @@ -433,308 +433,6 @@ c: } } -void GraphicsWindow::UpdateDraggedPoint(hEntity hp, double mx, double my) { - Entity *p = SS.GetEntity(hp); - Vector pos = p->PointGetNum(); - UpdateDraggedNum(&pos, mx, my); - p->PointForceTo(pos); -} - -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)); - - orig.mouse.x = mx; - orig.mouse.y = my; - InvalidateGraphics(); -} - -void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, - bool middleDown, bool rightDown, bool shiftDown, bool ctrlDown) -{ - if(GraphicsEditControlIsVisible()) return; - - 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(); - - double dx = (x - orig.mouse.x) / scale; - double dy = (y - orig.mouse.y) / scale; - - // When the view is locked, permit only translation (pan). - if(!(shiftDown || ctrlDown)) { - offset.x = orig.offset.x + dx*projRight.x + dy*projUp.x; - offset.y = orig.offset.y + dx*projRight.y + dy*projUp.y; - offset.z = orig.offset.z + dx*projRight.z + dy*projUp.z; - } else if(ctrlDown) { - double theta = atan2(orig.mouse.y, orig.mouse.x); - theta -= atan2(y, x); - - Vector normal = orig.projRight.Cross(orig.projUp); - projRight = orig.projRight.RotatedAbout(normal, theta); - projUp = orig.projUp.RotatedAbout(normal, theta); - - NormalizeProjectionVectors(); - } else { - 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); - - NormalizeProjectionVectors(); - } - - orig.projRight = projRight; - orig.projUp = projUp; - orig.offset = offset; - orig.mouse.x = x; - orig.mouse.y = y; - - InvalidateGraphics(); - return; - } - - 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; - } - if(pending.operation != 0) { - // We just started a drag, so remember for the undo before - // the drag changes anything. - SS.UndoRemember(); - } - } else { - // Otherwise, just hit test and give up; but don't hit test - // if the mouse is down, because then the user could hover - // a point, mouse down (thus selecting it), and drag, in an - // effort to drag the point, but instead hover a different - // entity before we move far enough to start the drag. - if(!leftDown) HitTestMakeSelection(mp); - } - return; - } - - // 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: { - Entity *p = SS.GetEntity(pending.point); - if((p->type == Entity::POINT_N_ROT_TRANS) && - (shiftDown || ctrlDown)) - { - // These points also come with a rotation, which the user can - // edit by pressing shift or control. - Quaternion q = p->PointGetQuaternion(); - Vector p3 = p->PointGetNum(); - Point2d p2 = ProjectPoint(p3); - - Vector u = q.RotationU(), v = q.RotationV(); - if(ctrlDown) { - double d = mp.DistanceTo(p2); - if(d < 25) { - // Don't start dragging the position about the normal - // until we're a little ways out, to get a reasonable - // reference pos - orig.mouse = mp; - break; - } - double theta = atan2(orig.mouse.y-p2.y, orig.mouse.x-p2.x); - theta -= atan2(y-p2.y, x-p2.x); - - Vector gn = projRight.Cross(projUp); - u = u.RotatedAbout(gn, -theta); - v = v.RotatedAbout(gn, -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(projUp, -s*dx); - u = u.RotatedAbout(projRight, s*dy); - v = v.RotatedAbout(projUp, -s*dx); - v = v.RotatedAbout(projRight, s*dy); - } - q = Quaternion::From(u, v); - p->PointForceQuaternionTo(q); - // Let's rotate about the selected point; so fix up the - // translation so that that point didn't move. - p->PointForceTo(p3); - orig.mouse = mp; - } else { - UpdateDraggedPoint(pending.point, x, y); - HitTestMakeSelection(mp); - } - SS.MarkGroupDirtyByEntity(pending.point); - 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); - - SS.MarkGroupDirtyByEntity(pending.point); - break; - } - case DRAGGING_NEW_ARC_POINT: { - UpdateDraggedPoint(pending.point, x, y); - HitTestMakeSelection(mp); - - hRequest hr = pending.point.request(); - Vector ona = SS.GetEntity(hr.entity(2))->PointGetNum(); - Vector onb = SS.GetEntity(hr.entity(3))->PointGetNum(); - Vector center = (ona.Plus(onb)).ScaledBy(0.5); - - SS.GetEntity(hr.entity(1))->PointForceTo(center); - - SS.MarkGroupDirtyByEntity(pending.point); - 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); - double r = c2.DistanceTo(mp)/scale; - SS.GetEntity(circle->distance)->DistanceForceTo(r); - - SS.MarkGroupDirtyByEntity(pending.circle); - 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 = projRight.Cross(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(projUp, -s*dx); - u = u.RotatedAbout(projRight, s*dy); - v = v.RotatedAbout(projUp, -s*dx); - v = v.RotatedAbout(projRight, s*dy); - } - orig.mouse = mp; - normal->NormalForceTo(Quaternion::From(u, v)); - - SS.MarkGroupDirtyByEntity(pending.normal); - break; - } - - default: oops(); - } - if(pending.operation != 0 && pending.operation != DRAGGING_CONSTRAINT) { - SS.GenerateAll(); - } - havePainted = false; -} - -bool GraphicsWindow::Selection::Equals(Selection *b) { - if(entity.v != b->entity.v) return false; - if(constraint.v != b->constraint.v) return false; - return true; -} -bool GraphicsWindow::Selection::IsEmpty(void) { - if(entity.v) return false; - if(constraint.v) return false; - return true; -} -void GraphicsWindow::Selection::Clear(void) { - entity.v = constraint.v = 0; - emphasized = false; -} -void GraphicsWindow::Selection::Draw(void) { - Vector refp; - if(entity.v) { - glLineWidth(1.5); - Entity *e = SS.GetEntity(entity); - e->Draw(); - if(emphasized) refp = e->GetReferencePos(); - glLineWidth(1); - } - if(constraint.v) { - Constraint *c = SS.GetConstraint(constraint); - c->Draw(); - if(emphasized) refp = c->GetReferencePos(); - } - if(emphasized && (constraint.v || entity.v)) { - double s = 0.501/SS.GW.scale; - Vector topLeft = SS.GW.projRight.ScaledBy(-SS.GW.width*s); - topLeft = topLeft.Plus(SS.GW.projUp.ScaledBy(SS.GW.height*s)); - topLeft = topLeft.Minus(SS.GW.offset); - - glLineWidth(40); - glColor4d(1.0, 1.0, 0, 0.2); - glBegin(GL_LINES); - glxVertex3v(topLeft); - glxVertex3v(refp); - glEnd(); - glLineWidth(1); - } -} - void GraphicsWindow::ClearSuper(void) { HideGraphicsEditControl(); ClearPending(); @@ -743,459 +441,6 @@ void GraphicsWindow::ClearSuper(void) { EnsureValidActives(); } -void GraphicsWindow::ClearPending(void) { - memset(&pending, 0, sizeof(pending)); -} - -void GraphicsWindow::HitTestMakeSelection(Point2d mp) { - int i; - double d, dmin = 1e12; - Selection s; - ZERO(&s); - - // Do the entities - 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 == pending.point.request().v) continue; - - d = e->GetDistance(mp); - if(d < 10 && d < dmin) { - memset(&s, 0, sizeof(s)); - s.entity = e->h; - dmin = d; - } - } - - // Constraints - for(i = 0; i < SS.constraint.n; i++) { - d = SS.constraint.elem[i].GetDistance(mp); - if(d < 10 && d < dmin) { - memset(&s, 0, sizeof(s)); - s.constraint = SS.constraint.elem[i].h; - dmin = d; - } - } - - // Faces, from the triangle mesh; these are lowest priority - if(s.constraint.v == 0 && s.entity.v == 0 && showShaded && showFaces) { - SMesh *m = &((SS.GetGroup(activeGroup))->mesh); - DWORD v = m->FirstIntersectionWith(mp); - if(v) { - s.entity.v = v; - } - } - - if(!s.Equals(&hover)) { - hover = s; - InvalidateGraphics(); - } -} - -void GraphicsWindow::ClearSelection(void) { - for(int i = 0; i < MAX_SELECTED; i++) { - selection[i].Clear(); - } - SS.later.showTW = true; - 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; - for(i = 0; i < MAX_SELECTED; i++) { - Selection *s = &(selection[i]); - if(s->entity.v) { - (gs.n)++; - - Entity *e = SS.entity.FindById(s->entity); - // A list of points, and a list of all entities that aren't points. - if(e->IsPoint()) { - gs.point[(gs.points)++] = s->entity; - } else { - gs.entity[(gs.entities)++] = s->entity; - } - - // And an auxiliary list of normals, including normals from - // workplanes. - if(e->IsNormal()) { - gs.anyNormal[(gs.anyNormals)++] = s->entity; - } else if(e->IsWorkplane()) { - gs.anyNormal[(gs.anyNormals)++] = e->Normal()->h; - } - - // And of vectors (i.e., stuff with a direction to constrain) - if(e->HasVector()) { - gs.vector[(gs.vectors)++] = s->entity; - } - - // Faces (which are special, associated/drawn with triangles) - if(e->IsFace()) { - gs.face[(gs.faces)++] = s->entity; - } - - // And some aux counts too - switch(e->type) { - case Entity::WORKPLANE: (gs.workplanes)++; break; - case Entity::LINE_SEGMENT: (gs.lineSegments)++; break; - - case Entity::ARC_OF_CIRCLE: - case Entity::CIRCLE: (gs.circlesOrArcs)++; break; - } - } - if(s->constraint.v) { - gs.constraint[(gs.constraints)++] = s->constraint; - } - } -} - -void GraphicsWindow::MouseMiddleDown(double x, double y) { - if(GraphicsEditControlIsVisible()) return; - - orig.offset = offset; - orig.projUp = projUp; - orig.projRight = projRight; - orig.mouse.x = x; - orig.mouse.y = y; -} - -hRequest GraphicsWindow::AddRequest(int type) { - return AddRequest(type, true); -} -hRequest GraphicsWindow::AddRequest(int type, bool rememberForUndo) { - if(rememberForUndo) SS.UndoRemember(); - - Request r; - memset(&r, 0, sizeof(r)); - r.group = activeGroup; - Group *g = SS.GetGroup(activeGroup); - if(g->type == Group::DRAWING_3D || g->type == Group::DRAWING_WORKPLANE) { - r.construction = false; - } else { - r.construction = true; - } - r.workplane = ActiveWorkplane(); - r.type = type; - SS.request.AddAndAssignId(&r); - - // 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(-1, -1); - SS.MarkGroupDirty(r.group); - 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->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; - - // Make sure the hover is up to date. - MouseMoved(mx, my, false, false, false, false, false); - orig.mouse.x = mx; - orig.mouse.y = my; - - // The current mouse location - Vector v = offset.ScaledBy(-1); - v = v.Plus(projRight.ScaledBy(mx/scale)); - v = v.Plus(projUp.ScaledBy(my/scale)); - - hRequest hr; - switch(pending.operation) { - case MNU_DATUM_POINT: - hr = AddRequest(Request::DATUM_POINT); - SS.GetEntity(hr.entity(0))->PointForceTo(v); - ConstrainPointByHovered(hr.entity(0)); - - ClearSuper(); - - pending.operation = 0; - break; - - case MNU_LINE_SEGMENT: - hr = AddRequest(Request::LINE_SEGMENT); - SS.GetEntity(hr.entity(1))->PointForceTo(v); - ConstrainPointByHovered(hr.entity(1)); - - ClearSuper(); - - 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_RECTANGLE: { - if(!SS.GW.LockedInWorkplane()) { - Error("Can't draw rectangle in 3d; select a workplane first."); - break; - } - hRequest lns[4]; - int i; - SS.UndoRemember(); - for(i = 0; i < 4; i++) { - lns[i] = AddRequest(Request::LINE_SEGMENT, false); - } - for(i = 0; i < 4; i++) { - Constraint::ConstrainCoincident( - lns[i].entity(1), lns[(i+1)%4].entity(2)); - SS.GetEntity(lns[i].entity(1))->PointForceTo(v); - SS.GetEntity(lns[i].entity(2))->PointForceTo(v); - } - for(i = 0; i < 4; i++) { - Constraint::Constrain( - (i % 2) ? Constraint::HORIZONTAL : Constraint::VERTICAL, - Entity::NO_ENTITY, Entity::NO_ENTITY, - lns[i].entity(0)); - } - ConstrainPointByHovered(lns[2].entity(1)); - - pending.operation = DRAGGING_NEW_POINT; - pending.point = lns[1].entity(2); - pending.description = "click to place other corner of rectangle"; - break; - } - case MNU_CIRCLE: - hr = AddRequest(Request::CIRCLE); - SS.GetEntity(hr.entity(1))->PointForceTo(v); - SS.GetEntity(hr.entity(32))->NormalForceTo( - Quaternion::From(SS.GW.projRight, SS.GW.projUp)); - ConstrainPointByHovered(hr.entity(1)); - - ClearSuper(); - - 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_ARC: - if(!SS.GW.LockedInWorkplane()) { - Error("Can't draw arc in 3d; select a workplane first."); - ClearPending(); - break; - } - hr = AddRequest(Request::ARC_OF_CIRCLE); - SS.GetEntity(hr.entity(1))->PointForceTo(v); - SS.GetEntity(hr.entity(2))->PointForceTo(v); - SS.GetEntity(hr.entity(3))->PointForceTo(v); - ConstrainPointByHovered(hr.entity(2)); - - ClearSuper(); - - pending.operation = DRAGGING_NEW_ARC_POINT; - pending.point = hr.entity(3); - pending.description = "click to place point"; - break; - - case MNU_CUBIC: - hr = AddRequest(Request::CUBIC); - SS.GetEntity(hr.entity(1))->PointForceTo(v); - SS.GetEntity(hr.entity(2))->PointForceTo(v); - SS.GetEntity(hr.entity(3))->PointForceTo(v); - SS.GetEntity(hr.entity(4))->PointForceTo(v); - ConstrainPointByHovered(hr.entity(1)); - - ClearSuper(); - - pending.operation = DRAGGING_NEW_CUBIC_POINT; - pending.point = hr.entity(4); - pending.description = "click to place next point of cubic"; - break; - - case MNU_WORKPLANE: - hr = AddRequest(Request::WORKPLANE); - SS.GetEntity(hr.entity(1))->PointForceTo(v); - SS.GetEntity(hr.entity(32))->NormalForceTo( - Quaternion::From(SS.GW.projRight, SS.GW.projUp)); - ConstrainPointByHovered(hr.entity(1)); - - ClearSuper(); - break; - - case DRAGGING_RADIUS: - case DRAGGING_NEW_POINT: - // The MouseMoved event has already dragged it as desired. - ClearPending(); - break; - - case DRAGGING_NEW_ARC_POINT: - case DRAGGING_NEW_CUBIC_POINT: - ConstrainPointByHovered(pending.point); - ClearPending(); - break; - - case DRAGGING_NEW_LINE_POINT: { - if(ConstrainPointByHovered(pending.point)) { - ClearPending(); - break; - } - // Create a new line segment, so that we continue drawing. - hRequest hr = AddRequest(Request::LINE_SEGMENT); - SS.GetEntity(hr.entity(1))->PointForceTo(v); - SS.GetEntity(hr.entity(2))->PointForceTo(v); - - // Constrain the line segments to share an endpoint - Constraint::ConstrainCoincident(pending.point, hr.entity(1)); - - // And drag an endpoint of the new line segment - pending.operation = DRAGGING_NEW_LINE_POINT; - pending.point = hr.entity(2); - pending.description = "click to place next point of next line"; - - break; - } - - case 0: - default: { - ClearPending(); - - if(hover.IsEmpty()) break; - - // If an item is hovered, then by clicking on it, we toggle its - // selection state. - int i; - for(i = 0; i < MAX_SELECTED; i++) { - if(selection[i].Equals(&hover)) { - selection[i].Clear(); - break; - } - } - if(i != MAX_SELECTED) break; - - if(hover.entity.v != 0 && SS.GetEntity(hover.entity)->IsFace()) { - // In the interest of speed for the triangle drawing code, - // only two faces may be selected at a time. - int c = 0; - for(i = 0; i < MAX_SELECTED; i++) { - hEntity he = selection[i].entity; - if(he.v != 0 && SS.GetEntity(he)->IsFace()) { - c++; - if(c >= 2) selection[i].Clear(); - } - } - } - - for(i = 0; i < MAX_SELECTED; i++) { - if(selection[i].IsEmpty()) { - selection[i] = hover; - break; - } - } - break; - } - } - - SS.later.showTW = true; - InvalidateGraphics(); -} - -void GraphicsWindow::MouseLeftUp(double mx, double my) { - switch(pending.operation) { - case DRAGGING_POINT: - case DRAGGING_CONSTRAINT: - case DRAGGING_NORMAL: - case DRAGGING_RADIUS: - ClearPending(); - break; - - default: - break; // do nothing - } -} - -void GraphicsWindow::MouseLeftDoubleClick(double mx, double my) { - if(GraphicsEditControlIsVisible()) return; - - if(hover.constraint.v) { - constraintBeingEdited = hover.constraint; - ClearSuper(); - - Constraint *c = SS.GetConstraint(constraintBeingEdited); - Vector p3 = c->GetLabelPos(); - Point2d p2 = ProjectPoint(p3); - ShowGraphicsEditControl((int)p2.x, (int)p2.y, c->exprA->Print()); - } -} - -void GraphicsWindow::EditControlDone(char *s) { - Expr *e = Expr::From(s); - if(e) { - SS.UndoRemember(); - - Constraint *c = SS.GetConstraint(constraintBeingEdited); - Expr::FreeKeep(&(c->exprA)); - c->exprA = e->DeepCopyKeep(); - - HideGraphicsEditControl(); - SS.MarkGroupDirty(c->group); - SS.GenerateAll(); - } else { - Error("Not a valid number or expression: '%s'", s); - } -} - -void GraphicsWindow::MouseScroll(double x, double y, int delta) { - double offsetRight = offset.Dot(projRight); - double offsetUp = offset.Dot(projUp); - - double righti = x/scale - offsetRight; - double upi = y/scale - offsetUp; - - if(delta > 0) { - scale *= 1.2; - } else { - scale /= 1.2; - } - - double rightf = x/scale - offsetRight; - double upf = y/scale - offsetUp; - - offset = offset.Plus(projRight.ScaledBy(rightf - righti)); - offset = offset.Plus(projUp.ScaledBy(upf - upi)); - - InvalidateGraphics(); -} - void GraphicsWindow::ToggleBool(int link, DWORD v) { bool *vb = (bool *)v; *vb = !*vb; @@ -1209,93 +454,3 @@ void GraphicsWindow::ToggleBool(int link, DWORD v) { SS.later.showTW = true; } -Vector GraphicsWindow::VectorFromProjs(double right, double up, double fwd) { - Vector n = projRight.Cross(projUp); - Vector r = offset.ScaledBy(-1); - r = r.Plus(projRight.ScaledBy(right)); - r = r.Plus(projUp.ScaledBy(up)); - r = r.Plus(n.ScaledBy(fwd)); - return r; -} - -void GraphicsWindow::Paint(int w, int h) { - havePainted = true; - width = w; height = h; - - glViewport(0, 0, w, h); - - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - - glScaled(scale*2.0/w, scale*2.0/h, scale*1.0/30000); - - double tx = projRight.Dot(offset); - double ty = projUp.Dot(offset); - Vector n = projUp.Cross(projRight); - double tz = n.Dot(offset); - double mat[16]; - MakeMatrix(mat, projRight.x, projRight.y, projRight.z, tx, - projUp.x, projUp.y, projUp.z, ty, - n.x, n.y, n.z, tz, - 0, 0, 0, 1); - glMultMatrixd(mat); - - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - - glShadeModel(GL_SMOOTH); - - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glEnable(GL_BLEND); - glEnable(GL_LINE_SMOOTH); - glEnable(GL_POLYGON_OFFSET_LINE); - glEnable(GL_POLYGON_OFFSET_FILL); - glEnable(GL_DEPTH_TEST); - glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); - glEnable(GL_NORMALIZE); - - // At the same depth, we want later lines drawn over earlier. - glDepthFunc(GL_LEQUAL); - - glClearDepth(1.0); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - Vector light = VectorFromProjs(-0.49*w/scale, 0.49*h/scale, 0); - GLfloat lightPos[4] = - { (GLfloat)light.x, (GLfloat)light.y, (GLfloat)light.z, 0 }; - glLightfv(GL_LIGHT0, GL_POSITION, lightPos); - glEnable(GL_LIGHT0); - - glLightModelf(GL_LIGHT_MODEL_TWO_SIDE, 1); - GLfloat ambient[4] = { 0.4f, 0.4f, 0.4f, 1.0f }; - glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient); - - glxUnlockColor(); - - int i; - // Draw the groups; this fills the polygons in a drawing group, and - // draws the solid mesh. - (SS.GetGroup(activeGroup))->Draw(); - - // Now draw the entities - if(showHdnLines) glDisable(GL_DEPTH_TEST); - Entity::DrawAll(); - - glDisable(GL_DEPTH_TEST); - // Draw the constraints - for(i = 0; i < SS.constraint.n; i++) { - SS.constraint.elem[i].Draw(); - } - - // Then redraw whatever the mouse is hovering over, highlighted. - glDisable(GL_DEPTH_TEST); - glxLockColorTo(1, 1, 0); - hover.Draw(); - - // And finally draw the selection, same mechanism. - glxLockColorTo(1, 0, 0); - for(i = 0; i < MAX_SELECTED; i++) { - selection[i].Draw(); - } -} - diff --git a/sketch.cpp b/group.cpp similarity index 60% rename from sketch.cpp rename to group.cpp index 3a993d0..44c2fe7 100644 --- a/sketch.cpp +++ b/group.cpp @@ -1,14 +1,9 @@ #include "solvespace.h" -const hEntity Entity::FREE_IN_3D = { 0 }; -const hEntity Entity::NO_ENTITY = { 0 }; const hParam Param::NO_PARAM = { 0 }; #define NO_PARAM (Param::NO_PARAM) const hGroup Group::HGROUP_REFERENCES = { 1 }; -const hRequest Request::HREQUEST_REFERENCE_XY = { 1 }; -const hRequest Request::HREQUEST_REFERENCE_YZ = { 2 }; -const hRequest Request::HREQUEST_REFERENCE_ZX = { 3 }; #define gs (SS.GW.gs) @@ -602,402 +597,3 @@ void Group::TagEdgesFromLineSegments(SEdgeList *el) { } } -void Group::GeneratePolygon(void) { - poly.Clear(); - - if(type == DRAWING_3D || type == DRAWING_WORKPLANE || - type == ROTATE || type == TRANSLATE) - { - SEdgeList edges; ZERO(&edges); - int i; - for(i = 0; i < SS.entity.n; i++) { - Entity *e = &(SS.entity.elem[i]); - if(e->group.v != h.v) continue; - - e->GenerateEdges(&edges); - } - SEdge error; - if(edges.AssemblePolygon(&poly, &error)) { - polyError.yes = false; - poly.normal = poly.ComputeNormal(); - poly.FixContourDirections(); - } else { - polyError.yes = true; - polyError.notClosedAt = error; - poly.Clear(); - } - edges.Clear(); - } -} - -void Group::GenerateMesh(void) { - SMesh outm; - ZERO(&outm); - if(type == EXTRUDE) { - SEdgeList edges; - ZERO(&edges); - int i; - Group *src = SS.GetGroup(opA); - Vector translate = Vector::From(h.param(0), h.param(1), h.param(2)); - - Vector tbot, ttop; - if(subtype == ONE_SIDED) { - tbot = Vector::From(0, 0, 0); ttop = translate.ScaledBy(2); - } else { - tbot = translate.ScaledBy(-1); ttop = translate.ScaledBy(1); - } - bool flipBottom = translate.Dot(src->poly.normal) > 0; - - // Get a triangulation of the source poly; this is not a closed mesh. - SMesh srcm; ZERO(&srcm); - (src->poly).TriangulateInto(&srcm); - - STriMeta meta = { 0, color }; - - // Do the bottom; that has normal pointing opposite from translate - meta.face = Remap(Entity::NO_ENTITY, REMAP_BOTTOM).v; - for(i = 0; i < srcm.l.n; i++) { - STriangle *st = &(srcm.l.elem[i]); - Vector at = (st->a).Plus(tbot), - bt = (st->b).Plus(tbot), - ct = (st->c).Plus(tbot); - if(flipBottom) { - outm.AddTriangle(meta, ct, bt, at); - } else { - outm.AddTriangle(meta, at, bt, ct); - } - } - // And the top; that has the normal pointing the same dir as translate - meta.face = Remap(Entity::NO_ENTITY, REMAP_TOP).v; - for(i = 0; i < srcm.l.n; i++) { - STriangle *st = &(srcm.l.elem[i]); - Vector at = (st->a).Plus(ttop), - bt = (st->b).Plus(ttop), - ct = (st->c).Plus(ttop); - if(flipBottom) { - outm.AddTriangle(meta, at, bt, ct); - } else { - outm.AddTriangle(meta, ct, bt, at); - } - } - srcm.Clear(); - // Get the source polygon to extrude, and break it down to edges - edges.Clear(); - (src->poly).MakeEdgesInto(&edges); - - edges.l.ClearTags(); - TagEdgesFromLineSegments(&edges); - // The sides; these are quads, represented as two triangles. - for(i = 0; i < edges.l.n; i++) { - SEdge *edge = &(edges.l.elem[i]); - Vector abot = (edge->a).Plus(tbot), bbot = (edge->b).Plus(tbot); - Vector atop = (edge->a).Plus(ttop), btop = (edge->b).Plus(ttop); - // We tagged the edges that came from line segments; their - // triangles should be associated with that plane face. - if(edge->tag) { - hEntity hl = { edge->tag }; - hEntity hf = Remap(hl, REMAP_LINE_TO_FACE); - meta.face = hf.v; - } else { - meta.face = 0; - } - if(flipBottom) { - outm.AddTriangle(meta, bbot, abot, atop); - outm.AddTriangle(meta, bbot, atop, btop); - } else { - outm.AddTriangle(meta, abot, bbot, atop); - outm.AddTriangle(meta, bbot, btop, atop); - } - } - edges.Clear(); - } else if(type == IMPORTED) { - // Triangles are just copied over, with the appropriate transformation - // applied. - Vector offset = { - SS.GetParam(h.param(0))->val, - SS.GetParam(h.param(1))->val, - SS.GetParam(h.param(2))->val }; - Quaternion q = { - SS.GetParam(h.param(3))->val, - SS.GetParam(h.param(4))->val, - SS.GetParam(h.param(5))->val, - SS.GetParam(h.param(6))->val }; - - for(int i = 0; i < impMesh.l.n; i++) { - STriangle st = impMesh.l.elem[i]; - - if(st.meta.face != 0) { - hEntity he = { st.meta.face }; - st.meta.face = Remap(he, 0).v; - } - st.a = q.Rotate(st.a).Plus(offset); - st.b = q.Rotate(st.b).Plus(offset); - st.c = q.Rotate(st.c).Plus(offset); - outm.AddTriangle(&st); - } - } - - // So our group's mesh appears in outm. Combine this with the previous - // group's mesh, using the requested operation. - mesh.Clear(); - bool prevMeshError = meshError.yes; - meshError.yes = false; - meshError.interferesAt.Clear(); - SMesh *a = PreviousGroupMesh(); - if(meshCombine == COMBINE_AS_UNION) { - mesh.MakeFromUnion(a, &outm); - } else if(meshCombine == COMBINE_AS_DIFFERENCE) { - mesh.MakeFromDifference(a, &outm); - } else { - if(!mesh.MakeFromInterferenceCheck(a, &outm, &(meshError.interferesAt))) - meshError.yes = true; - // And the list of failed triangles appears in meshError.interferesAt - } - if(prevMeshError != meshError.yes) { - // The error is reported in the text window for the group. - SS.later.showTW = true; - } - outm.Clear(); -} - -SMesh *Group::PreviousGroupMesh(void) { - int i; - for(i = 0; i < SS.group.n; i++) { - Group *g = &(SS.group.elem[i]); - if(g->h.v == h.v) break; - } - if(i == 0 || i >= SS.group.n) oops(); - return &(SS.group.elem[i-1].mesh); -} - - -void Group::Draw(void) { - // Show this even if the group is not visible. It's already possible - // to show or hide just this with the "show solids" flag. - - int specColor; - if(type != EXTRUDE && type != IMPORTED) { - specColor = RGB(25, 25, 25); // force the color to something dim - } else { - specColor = -1; // use the model color - } - // The back faces are drawn in red; should never seem them, since we - // draw closed shells, so that's a debugging aid. - GLfloat mpb[] = { 1.0f, 0.1f, 0.1f, 1.0 }; - glMaterialfv(GL_BACK, GL_AMBIENT_AND_DIFFUSE, mpb); - - // When we fill the mesh, we need to know which triangles are selected - // or hovered, in order to draw them differently. - DWORD mh = 0, ms1 = 0, ms2 = 0; - hEntity he = SS.GW.hover.entity; - if(he.v != 0 && SS.GetEntity(he)->IsFace()) { - mh = he.v; - } - SS.GW.GroupSelection(); - if(gs.faces > 0) ms1 = gs.face[0].v; - if(gs.faces > 1) ms2 = gs.face[1].v; - - glEnable(GL_LIGHTING); - if(SS.GW.showShaded) glxFillMesh(specColor, &mesh, mh, ms1, ms2); - glDisable(GL_LIGHTING); - - if(meshError.yes) { - // Draw the error triangles in bright red stripes, with no Z buffering - GLubyte mask[32*32/8]; - memset(mask, 0xf0, sizeof(mask)); - glPolygonStipple(mask); - - int specColor = 0; - glDisable(GL_DEPTH_TEST); - glColor3d(0, 0, 0); - glxFillMesh(0, &meshError.interferesAt, 0, 0, 0); - glEnable(GL_POLYGON_STIPPLE); - glColor3d(1, 0, 0); - glxFillMesh(0, &meshError.interferesAt, 0, 0, 0); - glEnable(GL_DEPTH_TEST); - glDisable(GL_POLYGON_STIPPLE); - } - - if(SS.GW.showMesh) glxDebugMesh(&mesh); - - if(!SS.GW.showShaded) return; - if(polyError.yes) { - glxColor4d(1, 0, 0, 0.2); - glLineWidth(10); - glBegin(GL_LINES); - glxVertex3v(polyError.notClosedAt.a); - glxVertex3v(polyError.notClosedAt.b); - glEnd(); - glLineWidth(1); - glxColor3d(1, 0, 0); - glPushMatrix(); - glxTranslatev(polyError.notClosedAt.b); - glxOntoWorkplane(SS.GW.projRight, SS.GW.projUp); - glxWriteText("not closed contour!"); - glPopMatrix(); - } else { - glxColor4d(0, 0.1, 0.1, 0.5); - glPolygonOffset(-1, -1); - glxFillPolygon(&poly); - glPolygonOffset(0, 0); - } -} - -hParam Request::AddParam(IdList *param, hParam hp) { - Param pa; - memset(&pa, 0, sizeof(pa)); - pa.h = hp; - param->Add(&pa); - return hp; -} - -void Request::Generate(IdList *entity, - IdList *param) -{ - int points = 0; - int params = 0; - int et = 0; - bool hasNormal = false; - bool hasDistance = false; - int i; - - Entity e; - memset(&e, 0, sizeof(e)); - switch(type) { - case Request::WORKPLANE: - et = Entity::WORKPLANE; - points = 1; - hasNormal = true; - break; - - case Request::DATUM_POINT: - et = 0; - points = 1; - break; - - case Request::LINE_SEGMENT: - et = Entity::LINE_SEGMENT; - points = 2; - break; - - case Request::CIRCLE: - et = Entity::CIRCLE; - points = 1; - params = 1; - hasNormal = true; - hasDistance = true; - break; - - case Request::ARC_OF_CIRCLE: - et = Entity::ARC_OF_CIRCLE; - points = 3; - hasNormal = true; - break; - - case Request::CUBIC: - et = Entity::CUBIC; - points = 4; - break; - - default: oops(); - } - - // Generate the entity that's specific to this request. - e.type = et; - e.group = group; - e.workplane = workplane; - e.construction = construction; - 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(32); - 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; - } - if(hasDistance) { - Entity d; - memset(&d, 0, sizeof(d)); - d.workplane = workplane; - d.h = h.entity(64); - d.group = group; - d.type = Entity::DISTANCE; - d.param[0] = AddParam(param, h.param(64)); - entity->Add(&d); - e.distance = d.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) { - char *s; - if(h.v == Request::HREQUEST_REFERENCE_XY.v) { - s = "#XY"; - } else if(h.v == Request::HREQUEST_REFERENCE_YZ.v) { - s = "#YZ"; - } else if(h.v == Request::HREQUEST_REFERENCE_ZX.v) { - s = "#ZX"; - } else { - switch(type) { - case WORKPLANE: s = "workplane"; break; - case DATUM_POINT: s = "datum-point"; break; - case LINE_SEGMENT: s = "line-segment"; break; - case CUBIC: s = "cubic-bezier"; break; - case CIRCLE: s = "circle"; break; - case ARC_OF_CIRCLE: s = "arc-of-circle"; break; - default: s = "???"; break; - } - } - static char ret[100]; - sprintf(ret, "r%03x-%s", h.v, s); - return ret; -} - diff --git a/groupmesh.cpp b/groupmesh.cpp new file mode 100644 index 0000000..46f4b6a --- /dev/null +++ b/groupmesh.cpp @@ -0,0 +1,244 @@ +#include "solvespace.h" + +#define gs (SS.GW.gs) + +void Group::GeneratePolygon(void) { + poly.Clear(); + + if(type == DRAWING_3D || type == DRAWING_WORKPLANE || + type == ROTATE || type == TRANSLATE) + { + SEdgeList edges; ZERO(&edges); + int i; + for(i = 0; i < SS.entity.n; i++) { + Entity *e = &(SS.entity.elem[i]); + if(e->group.v != h.v) continue; + + e->GenerateEdges(&edges); + } + SEdge error; + if(edges.AssemblePolygon(&poly, &error)) { + polyError.yes = false; + poly.normal = poly.ComputeNormal(); + poly.FixContourDirections(); + } else { + polyError.yes = true; + polyError.notClosedAt = error; + poly.Clear(); + } + edges.Clear(); + } +} + +void Group::GenerateMesh(void) { + SMesh outm; + ZERO(&outm); + if(type == EXTRUDE) { + SEdgeList edges; + ZERO(&edges); + int i; + Group *src = SS.GetGroup(opA); + Vector translate = Vector::From(h.param(0), h.param(1), h.param(2)); + + Vector tbot, ttop; + if(subtype == ONE_SIDED) { + tbot = Vector::From(0, 0, 0); ttop = translate.ScaledBy(2); + } else { + tbot = translate.ScaledBy(-1); ttop = translate.ScaledBy(1); + } + bool flipBottom = translate.Dot(src->poly.normal) > 0; + + // Get a triangulation of the source poly; this is not a closed mesh. + SMesh srcm; ZERO(&srcm); + (src->poly).TriangulateInto(&srcm); + + STriMeta meta = { 0, color }; + + // Do the bottom; that has normal pointing opposite from translate + meta.face = Remap(Entity::NO_ENTITY, REMAP_BOTTOM).v; + for(i = 0; i < srcm.l.n; i++) { + STriangle *st = &(srcm.l.elem[i]); + Vector at = (st->a).Plus(tbot), + bt = (st->b).Plus(tbot), + ct = (st->c).Plus(tbot); + if(flipBottom) { + outm.AddTriangle(meta, ct, bt, at); + } else { + outm.AddTriangle(meta, at, bt, ct); + } + } + // And the top; that has the normal pointing the same dir as translate + meta.face = Remap(Entity::NO_ENTITY, REMAP_TOP).v; + for(i = 0; i < srcm.l.n; i++) { + STriangle *st = &(srcm.l.elem[i]); + Vector at = (st->a).Plus(ttop), + bt = (st->b).Plus(ttop), + ct = (st->c).Plus(ttop); + if(flipBottom) { + outm.AddTriangle(meta, at, bt, ct); + } else { + outm.AddTriangle(meta, ct, bt, at); + } + } + srcm.Clear(); + // Get the source polygon to extrude, and break it down to edges + edges.Clear(); + (src->poly).MakeEdgesInto(&edges); + + edges.l.ClearTags(); + TagEdgesFromLineSegments(&edges); + // The sides; these are quads, represented as two triangles. + for(i = 0; i < edges.l.n; i++) { + SEdge *edge = &(edges.l.elem[i]); + Vector abot = (edge->a).Plus(tbot), bbot = (edge->b).Plus(tbot); + Vector atop = (edge->a).Plus(ttop), btop = (edge->b).Plus(ttop); + // We tagged the edges that came from line segments; their + // triangles should be associated with that plane face. + if(edge->tag) { + hEntity hl = { edge->tag }; + hEntity hf = Remap(hl, REMAP_LINE_TO_FACE); + meta.face = hf.v; + } else { + meta.face = 0; + } + if(flipBottom) { + outm.AddTriangle(meta, bbot, abot, atop); + outm.AddTriangle(meta, bbot, atop, btop); + } else { + outm.AddTriangle(meta, abot, bbot, atop); + outm.AddTriangle(meta, bbot, btop, atop); + } + } + edges.Clear(); + } else if(type == IMPORTED) { + // Triangles are just copied over, with the appropriate transformation + // applied. + Vector offset = { + SS.GetParam(h.param(0))->val, + SS.GetParam(h.param(1))->val, + SS.GetParam(h.param(2))->val }; + Quaternion q = { + SS.GetParam(h.param(3))->val, + SS.GetParam(h.param(4))->val, + SS.GetParam(h.param(5))->val, + SS.GetParam(h.param(6))->val }; + + for(int i = 0; i < impMesh.l.n; i++) { + STriangle st = impMesh.l.elem[i]; + + if(st.meta.face != 0) { + hEntity he = { st.meta.face }; + st.meta.face = Remap(he, 0).v; + } + st.a = q.Rotate(st.a).Plus(offset); + st.b = q.Rotate(st.b).Plus(offset); + st.c = q.Rotate(st.c).Plus(offset); + outm.AddTriangle(&st); + } + } + + // So our group's mesh appears in outm. Combine this with the previous + // group's mesh, using the requested operation. + mesh.Clear(); + bool prevMeshError = meshError.yes; + meshError.yes = false; + meshError.interferesAt.Clear(); + SMesh *a = PreviousGroupMesh(); + if(meshCombine == COMBINE_AS_UNION) { + mesh.MakeFromUnion(a, &outm); + } else if(meshCombine == COMBINE_AS_DIFFERENCE) { + mesh.MakeFromDifference(a, &outm); + } else { + if(!mesh.MakeFromInterferenceCheck(a, &outm, &(meshError.interferesAt))) + meshError.yes = true; + // And the list of failed triangles appears in meshError.interferesAt + } + if(prevMeshError != meshError.yes) { + // The error is reported in the text window for the group. + SS.later.showTW = true; + } + outm.Clear(); +} + +SMesh *Group::PreviousGroupMesh(void) { + int i; + for(i = 0; i < SS.group.n; i++) { + Group *g = &(SS.group.elem[i]); + if(g->h.v == h.v) break; + } + if(i == 0 || i >= SS.group.n) oops(); + return &(SS.group.elem[i-1].mesh); +} + +void Group::Draw(void) { + // Show this even if the group is not visible. It's already possible + // to show or hide just this with the "show solids" flag. + + int specColor; + if(type != EXTRUDE && type != IMPORTED) { + specColor = RGB(25, 25, 25); // force the color to something dim + } else { + specColor = -1; // use the model color + } + // The back faces are drawn in red; should never seem them, since we + // draw closed shells, so that's a debugging aid. + GLfloat mpb[] = { 1.0f, 0.1f, 0.1f, 1.0 }; + glMaterialfv(GL_BACK, GL_AMBIENT_AND_DIFFUSE, mpb); + + // When we fill the mesh, we need to know which triangles are selected + // or hovered, in order to draw them differently. + DWORD mh = 0, ms1 = 0, ms2 = 0; + hEntity he = SS.GW.hover.entity; + if(he.v != 0 && SS.GetEntity(he)->IsFace()) { + mh = he.v; + } + SS.GW.GroupSelection(); + if(gs.faces > 0) ms1 = gs.face[0].v; + if(gs.faces > 1) ms2 = gs.face[1].v; + + glEnable(GL_LIGHTING); + if(SS.GW.showShaded) glxFillMesh(specColor, &mesh, mh, ms1, ms2); + glDisable(GL_LIGHTING); + + if(meshError.yes) { + // Draw the error triangles in bright red stripes, with no Z buffering + GLubyte mask[32*32/8]; + memset(mask, 0xf0, sizeof(mask)); + glPolygonStipple(mask); + + int specColor = 0; + glDisable(GL_DEPTH_TEST); + glColor3d(0, 0, 0); + glxFillMesh(0, &meshError.interferesAt, 0, 0, 0); + glEnable(GL_POLYGON_STIPPLE); + glColor3d(1, 0, 0); + glxFillMesh(0, &meshError.interferesAt, 0, 0, 0); + glEnable(GL_DEPTH_TEST); + glDisable(GL_POLYGON_STIPPLE); + } + + if(SS.GW.showMesh) glxDebugMesh(&mesh); + + if(!SS.GW.showShaded) return; + if(polyError.yes) { + glxColor4d(1, 0, 0, 0.2); + glLineWidth(10); + glBegin(GL_LINES); + glxVertex3v(polyError.notClosedAt.a); + glxVertex3v(polyError.notClosedAt.b); + glEnd(); + glLineWidth(1); + glxColor3d(1, 0, 0); + glPushMatrix(); + glxTranslatev(polyError.notClosedAt.b); + glxOntoWorkplane(SS.GW.projRight, SS.GW.projUp); + glxWriteText("not closed contour!"); + glPopMatrix(); + } else { + glxColor4d(0, 0.1, 0.1, 0.5); + glPolygonOffset(-1, -1); + glxFillPolygon(&poly); + glPolygonOffset(0, 0); + } +} + diff --git a/request.cpp b/request.cpp new file mode 100644 index 0000000..132bf7b --- /dev/null +++ b/request.cpp @@ -0,0 +1,165 @@ +#include "solvespace.h" + +const hRequest Request::HREQUEST_REFERENCE_XY = { 1 }; +const hRequest Request::HREQUEST_REFERENCE_YZ = { 2 }; +const hRequest Request::HREQUEST_REFERENCE_ZX = { 3 }; + + +void Request::Generate(IdList *entity, + IdList *param) +{ + int points = 0; + int params = 0; + int et = 0; + bool hasNormal = false; + bool hasDistance = false; + int i; + + Entity e; + memset(&e, 0, sizeof(e)); + switch(type) { + case Request::WORKPLANE: + et = Entity::WORKPLANE; + points = 1; + hasNormal = true; + break; + + case Request::DATUM_POINT: + et = 0; + points = 1; + break; + + case Request::LINE_SEGMENT: + et = Entity::LINE_SEGMENT; + points = 2; + break; + + case Request::CIRCLE: + et = Entity::CIRCLE; + points = 1; + params = 1; + hasNormal = true; + hasDistance = true; + break; + + case Request::ARC_OF_CIRCLE: + et = Entity::ARC_OF_CIRCLE; + points = 3; + hasNormal = true; + break; + + case Request::CUBIC: + et = Entity::CUBIC; + points = 4; + break; + + default: oops(); + } + + // Generate the entity that's specific to this request. + e.type = et; + e.group = group; + e.workplane = workplane; + e.construction = construction; + 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(32); + 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; + } + if(hasDistance) { + Entity d; + memset(&d, 0, sizeof(d)); + d.workplane = workplane; + d.h = h.entity(64); + d.group = group; + d.type = Entity::DISTANCE; + d.param[0] = AddParam(param, h.param(64)); + entity->Add(&d); + e.distance = d.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) { + char *s; + if(h.v == Request::HREQUEST_REFERENCE_XY.v) { + s = "#XY"; + } else if(h.v == Request::HREQUEST_REFERENCE_YZ.v) { + s = "#YZ"; + } else if(h.v == Request::HREQUEST_REFERENCE_ZX.v) { + s = "#ZX"; + } else { + switch(type) { + case WORKPLANE: s = "workplane"; break; + case DATUM_POINT: s = "datum-point"; break; + case LINE_SEGMENT: s = "line-segment"; break; + case CUBIC: s = "cubic-bezier"; break; + case CIRCLE: s = "circle"; break; + case ARC_OF_CIRCLE: s = "arc-of-circle"; break; + default: s = "???"; break; + } + } + static char ret[100]; + sprintf(ret, "r%03x-%s", h.v, s); + return ret; +} + +hParam Request::AddParam(IdList *param, hParam hp) { + Param pa; + memset(&pa, 0, sizeof(pa)); + pa.h = hp; + param->Add(&pa); + return hp; +} +