#include #include "solvespace.h" #define mView (&GraphicsWindow::MenuView) #define mEdit (&GraphicsWindow::MenuEdit) #define mReq (&GraphicsWindow::MenuRequest) #define mCon (&Constraint::MenuConstrain) #define mFile (&SolveSpace::MenuFile) #define S 0x100 #define C 0x200 const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = { { 0, "&File", 0, NULL }, { 1, "&New\tCtrl+N", MNU_NEW, 'N'|C, mFile }, { 1, "&Open...\tCtrl+O", MNU_OPEN, 'O'|C, mFile }, { 1, "&Save\tCtrl+S", MNU_SAVE, 'S'|C, mFile }, { 1, "Save &As...", MNU_SAVE_AS, 0, mFile }, { 1, NULL, 0, 0, NULL }, { 1, "E&xit", MNU_EXIT, 0, mFile }, { 0, "&Edit", 0, NULL }, { 1, "&Undo\tCtrl+Z", 0, NULL }, { 1, "&Redo\tCtrl+Y", 0, NULL }, { 1, NULL, 0, NULL }, { 1, "&Delete\tDel", MNU_DELETE, 127, mEdit }, { 1, NULL, 0, NULL }, { 1, "&Unselect All\tEsc", MNU_UNSELECT_ALL, 27, mEdit }, { 0, "&View", 0, NULL }, { 1, "Zoom &In\t+", MNU_ZOOM_IN, '+', mView }, { 1, "Zoom &Out\t-", MNU_ZOOM_OUT, '-', mView }, { 1, "Zoom To &Fit\tF", MNU_ZOOM_TO_FIT, 'F', mView }, { 1, NULL, 0, NULL }, { 1, "&Onto Plane / Coordinate System\tO", MNU_ORIENT_ONTO, 'O', mView }, { 1, "&Lock Orientation\tL", MNU_LOCK_VIEW, 'L', mView }, { 1, NULL, 0, NULL }, { 1, "Dimensions in &Inches", 0, NULL }, { 1, "Dimensions in &Millimeters", 0, NULL }, { 0, "&Request", 0, NULL }, { 1, "Dra&w in 2d Coordinate System\tW", MNU_SEL_CSYS, 'W', mReq }, { 1, "Draw Anywhere in 3d\tQ", MNU_NO_CSYS, 'Q', mReq }, { 1, NULL, 0, NULL }, { 1, "Datum &Point\tP", MNU_DATUM_POINT, 'P', mReq }, { 1, "Datum A&xis\tX", 0, 'X', mReq }, { 1, "Datum Pla&ne\tN", 0, 'N', mReq }, { 1, "2d Coordinate S&ystem\tY", 0, 'Y', mReq }, { 1, NULL, 0, NULL }, { 1, "Line &Segment\tS", MNU_LINE_SEGMENT, 'S', mReq }, { 1, "&Circle\tC", 0, 'C', mReq }, { 1, "&Arc of a Circle\tA", 0, 'A', mReq }, { 1, "&Cubic Segment\t3", 0, '3', mReq }, { 1, NULL, 0, NULL }, { 1, "Boolean &Union\tU", 0, 'U', mReq }, { 1, "Boolean &Difference\tD", 0, 'D', mReq }, { 1, "Step and Repeat &Translate\tT", 0, 'T', mReq }, { 1, "Step and Repeat &Rotate\tR", 0, 'R', mReq }, { 1, NULL, 0, NULL }, { 1, "Sym&bolic Variable\tB", 0, 'B', mReq }, { 1, "&Import From File...\tI", 0, 'I', mReq }, { 1, NULL, 0, NULL }, { 1, "To&ggle Construction\tG", 0, 'G', NULL }, { 0, "&Constrain", 0, NULL }, { 1, "&Distance / Diameter\tShift+D", MNU_DISTANCE_DIA, 'D'|S, mCon }, { 1, "A&ngle\tShift+N", 0, 'N'|S, NULL }, { 1, "Other S&upplementary Angle\tShift+U", 0, 'U'|S, NULL }, { 1, NULL, 0, NULL }, { 1, "&Horizontal\tShift+H", 0, 'H'|S, NULL }, { 1, "&Vertical\tShift+V", 0, 'V'|S, NULL }, { 1, NULL, 0, NULL }, { 1, "Coincident / &On Curve\tShift+O", 0, 'O'|S, NULL }, { 1, "E&qual Length / Radius\tShift+Q", 0, 'Q'|S, NULL }, { 1, "At &Midpoint\tShift+M", 0, 'M'|S, NULL }, { 1, "S&ymmetric\tShift+Y", 0, 'Y'|S, NULL }, { 1, NULL, 0, NULL }, { 1, "Sym&bolic Equation\tShift+B", 0, 'B'|S, NULL }, { 0, "&Help", 0, NULL }, { 1, "&About\t", 0, NULL }, { -1 }, }; void GraphicsWindow::Init(void) { memset(this, 0, sizeof(*this)); offset.x = offset.y = offset.z = 0; scale = 1; projRight.x = 1; projRight.y = projRight.z = 0; projUp.y = 1; projUp.z = projUp.x = 0; EnsureValidActives(); show2dCsyss = true; showAxes = true; showPoints = true; showAllGroups = true; showConstraints = true; } void GraphicsWindow::NormalizeProjectionVectors(void) { Vector norm = projRight.Cross(projUp); projUp = norm.Cross(projRight); projUp = projUp.ScaledBy(1/projUp.Magnitude()); projRight = projRight.ScaledBy(1/projRight.Magnitude()); } Point2d GraphicsWindow::ProjectPoint(Vector p) { p = p.Plus(offset); Point2d r; r.x = p.Dot(projRight) * scale; r.y = p.Dot(projUp) * scale; return r; } void GraphicsWindow::MenuView(int id) { switch(id) { case MNU_ZOOM_IN: SS.GW.scale *= 1.2; break; case MNU_ZOOM_OUT: SS.GW.scale /= 1.2; break; case MNU_ZOOM_TO_FIT: break; case MNU_LOCK_VIEW: SS.GW.viewLocked = !SS.GW.viewLocked; CheckMenuById(MNU_LOCK_VIEW, SS.GW.viewLocked); break; case MNU_ORIENT_ONTO: { SS.GW.GroupSelection(); Entity *e = NULL; if(SS.GW.gs.n == 1 && SS.GW.gs.csyss == 1) { e = SS.GetEntity(SS.GW.gs.entity[0]); } else if(SS.GW.activeCsys.v != Entity::NO_CSYS.v) { e = SS.GetEntity(SS.GW.activeCsys); } if(e) { // A quaternion with our original rotation Quaternion quat0 = Quaternion::MakeFrom( SS.GW.projRight, SS.GW.projUp); // And with our final rotation Vector pr, pu; e->Get2dCsysBasisVectors(&pr, &pu); Quaternion quatf = Quaternion::MakeFrom(pr, pu); // Make sure we take the shorter of the two possible paths. double mp = (quatf.Minus(quat0)).Magnitude(); double mm = (quatf.Plus(quat0)).Magnitude(); if(mp > mm) { quatf = quatf.ScaledBy(-1); mp = mm; } // And also get the offsets. Vector offset0 = SS.GW.offset; Vector offsetf = SS.GetEntity(e->assoc[0])->GetPointCoords(); // Animate transition, unless it's a tiny move. SDWORD dt = (mp < 0.01) ? (-20) : (SDWORD)(100 + 1000*mp); SDWORD tn, t0 = GetMilliseconds(); double s = 0; do { SS.GW.offset = (offset0.ScaledBy(1 - s)).Plus(offsetf.ScaledBy(s)); Quaternion quat = (quat0.ScaledBy(1 - s)).Plus(quatf.ScaledBy(s)); quat = quat.WithMagnitude(1); SS.GW.projRight = quat.RotationU(); SS.GW.projUp = quat.RotationV(); PaintGraphics(); tn = GetMilliseconds(); s = (tn - t0)/((double)dt); } while((tn - t0) < dt); SS.GW.projRight = pr; SS.GW.projUp = pu; SS.GW.offset = offsetf; SS.GW.hover.Clear(); SS.GW.ClearSelection(); InvalidateGraphics(); } else { Error("Select plane or coordinate system before orienting."); } break; } default: oops(); } InvalidateGraphics(); } void GraphicsWindow::EnsureValidActives(void) { bool change = false; // The active group must exist, and not be the references. Group *g = SS.group.FindByIdNoOops(activeGroup); if((!g) || (g->h.v == Group::HGROUP_REFERENCES.v)) { int i; for(i = 0; i < SS.group.n; i++) { if(SS.group.elem[i].h.v != Group::HGROUP_REFERENCES.v) { break; } } if(i >= SS.group.n) oops(); activeGroup = SS.group.elem[i].h; change = true; } // The active coordinate system must also exist. if(activeCsys.v != Entity::NO_CSYS.v && !SS.entity.FindByIdNoOops(activeCsys)) { activeCsys = Entity::NO_CSYS; change = true; } if(change) SS.TW.Show(); EnableMenuById(MNU_NO_CSYS, (activeCsys.v != Entity::NO_CSYS.v)); } void GraphicsWindow::MenuEdit(int id) { switch(id) { case MNU_UNSELECT_ALL: SS.GW.ClearSelection(); SS.GW.pendingOperation = 0; SS.GW.pendingDescription = NULL; SS.TW.Show(); break; case MNU_DELETE: { int i; SS.request.ClearTags(); for(i = 0; i < MAX_SELECTED; i++) { Selection *s = &(SS.GW.selection[i]); hRequest r; r.v = 0; if(s->entity.v) { r = s->entity.request(); } if(r.v) SS.request.Tag(r, 1); } SS.request.RemoveTagged(); SS.GenerateAll(); SS.GW.ClearSelection(); SS.GW.hover.Clear(); break; } default: oops(); } } void GraphicsWindow::MenuRequest(int id) { char *s; switch(id) { case MNU_SEL_CSYS: SS.GW.GroupSelection(); if(SS.GW.gs.n == 1 && SS.GW.gs.csyss == 1) { SS.GW.activeCsys = SS.GW.gs.entity[0]; SS.GW.ClearSelection(); } else { Error("Select 2d coordinate system (e.g., the XY plane) " "before locking on."); } SS.GW.EnsureValidActives(); SS.TW.Show(); break; case MNU_NO_CSYS: SS.GW.activeCsys = Entity::NO_CSYS; SS.GW.EnsureValidActives(); SS.TW.Show(); break; case MNU_DATUM_POINT: s = "click to place datum point"; goto c; case MNU_LINE_SEGMENT: s = "click first point of line segment"; goto c; c: SS.GW.pendingOperation = id; SS.GW.pendingDescription = s; SS.TW.Show(); break; default: oops(); } } void GraphicsWindow::UpdateDraggedEntity(hEntity hp, double mx, double my) { Entity *p = SS.GetEntity(hp); Vector pos = p->GetPointCoords(); UpdateDraggedPoint(&pos, mx, my); p->ForcePointTo(pos); } void GraphicsWindow::UpdateDraggedPoint(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) { Point2d mp = { x, y }; 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 || viewLocked) { 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 && !viewLocked) { 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 if(!viewLocked) { double s = 0.3*(PI/180); // 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(); } else if(leftDown) { // We are left-dragging. This is often used to drag points, or // constraint labels. if(hover.entity.v && SS.GetEntity(hover.entity)->IsPoint() && !SS.GetEntity(hover.entity)->IsFromReferences()) { ClearSelection(); UpdateDraggedEntity(hover.entity, x, y); } else if(hover.constraint.v && SS.GetConstraint(hover.constraint)->HasLabel()) { ClearSelection(); Constraint *c = SS.constraint.FindById(hover.constraint); UpdateDraggedPoint(&(c->disp.offset), x, y); } } else { if(pendingOperation == PENDING_OPERATION_DRAGGING_POINT) { UpdateDraggedEntity(pendingPoint, x, y); } else { // Do our usual hit testing, for the selection. Selection s; HitTestMakeSelection(mp, &s); if(!s.Equals(&hover)) { hover = s; 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; } void GraphicsWindow::Selection::Draw(void) { if(entity.v) SS.GetEntity (entity )->Draw(); if(constraint.v) SS.GetConstraint(constraint)->Draw(); } void GraphicsWindow::HitTestMakeSelection(Point2d mp, Selection *dest) { int i; double d, dmin = 1e12; memset(dest, 0, sizeof(*dest)); // Do the entities for(i = 0; i < SS.entity.n; i++) { d = SS.entity.elem[i].GetDistance(mp); if(d < 10 && d < dmin) { memset(dest, 0, sizeof(*dest)); dest->entity = SS.entity.elem[i].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(dest, 0, sizeof(*dest)); dest->constraint = SS.constraint.elem[i].h; dmin = d; } } } void GraphicsWindow::ClearSelection(void) { for(int i = 0; i < MAX_SELECTED; i++) { selection[i].Clear(); } InvalidateGraphics(); } void GraphicsWindow::GroupSelection(void) { gs.points = gs.entities = 0; gs.csyss = gs.lineSegments = 0; gs.n = 0; int i; for(i = 0; i < MAX_SELECTED; i++) { Selection *s = &(selection[i]); if(s->entity.v) { gs.entity[(gs.entities)++] = s->entity; (gs.n)++; Entity *e = SS.entity.FindById(s->entity); switch(e->type) { case Entity::CSYS_2D: (gs.csyss)++; break; case Entity::LINE_SEGMENT: (gs.lineSegments)++; break; } if(e->IsPoint()) { gs.point[(gs.points)++] = s->entity; } } } } void GraphicsWindow::MouseMiddleDown(double x, double y) { orig.offset = offset; orig.projUp = projUp; orig.projRight = projRight; orig.mouse.x = x; orig.mouse.y = y; } hRequest GraphicsWindow::AddRequest(int type) { Request r; memset(&r, 0, sizeof(r)); r.group = activeGroup; r.csys = activeCsys; r.type = type; SS.request.AddAndAssignId(&r); SS.GenerateAll(); return r.h; } void GraphicsWindow::MouseLeftDown(double mx, double my) { // 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(pendingOperation) { case MNU_DATUM_POINT: hr = AddRequest(Request::DATUM_POINT); SS.GetEntity(hr.entity(0))->ForcePointTo(v); pendingOperation = 0; break; case MNU_LINE_SEGMENT: hr = AddRequest(Request::LINE_SEGMENT); SS.GetEntity(hr.entity(1))->ForcePointTo(v); pendingOperation = PENDING_OPERATION_DRAGGING_POINT; pendingPoint = hr.entity(2); pendingDescription = "click to place next point of line"; SS.GetEntity(pendingPoint)->ForcePointTo(v); break; case PENDING_OPERATION_DRAGGING_POINT: // The MouseMoved event has already dragged it under the cursor. pendingOperation = 0; break; case 0: default: { pendingOperation = 0; 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; for(i = 0; i < MAX_SELECTED; i++) { if(selection[i].IsEmpty()) { selection[i] = hover; break; } } break; } } SS.TW.Show(); InvalidateGraphics(); } 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; SS.GenerateAll(); InvalidateGraphics(); SS.TW.Show(); } void GraphicsWindow::ToggleAnyDatumShown(int link, DWORD v) { bool t = !(SS.GW.show2dCsyss && SS.GW.showAxes && SS.GW.showPoints); SS.GW.show2dCsyss = t; SS.GW.showAxes = t; SS.GW.showPoints = t; SS.GenerateAll(); InvalidateGraphics(); SS.TW.Show(); } void GraphicsWindow::Paint(int w, int h) { width = w; height = h; glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glScaled(scale*2.0/w, scale*2.0/h, 0); double tx = projRight.Dot(offset); double ty = projUp.Dot(offset); double mat[16]; MakeMatrix(mat, projRight.x, projRight.y, projRight.z, tx, projUp.x, projUp.y, projUp.z, ty, 0, 0, 0, 0, 0, 0, 0, 1); glMultMatrixd(mat); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); glEnable(GL_LINE_SMOOTH); glEnable(GL_DEPTH_TEST); glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); glClearIndex((GLfloat)0); glClearDepth(1.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); int i; // First, draw the entire scene. glxUnlockColor(); for(i = 0; i < SS.entity.n; i++) { SS.entity.elem[i].Draw(); } for(i = 0; i < SS.constraint.n; i++) { SS.constraint.elem[i].Draw(); } // Then redraw whatever the mouse is hovering over, highlighted. Have // to disable the depth test, so that we can overdraw. 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(); } }