
points are now entities like any others; so a line segment request will generate three entities, the line segment and its endpoints. I think that's cleaner. When I do step and repeats (and imports, and other similar), I'll need a consistent way to assign those entity ids. That assignment must not change if the sketch is edited. I don't have a clean way to do that; best thought right now is to keep a record of what maps have been used previously, and not pick a new map as long as it's possible to use one that was used previously. This all means that more crap gets pushed in to the Entity structure, so that they can keep track of what solver variables define them. Still seems better, though. I'm closer to ready to start solving. [git-p4: depot-paths = "//depot/solvespace/": change = 1673]
639 lines
21 KiB
C++
639 lines
21 KiB
C++
#include <stdarg.h>
|
|
|
|
#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();
|
|
}
|
|
}
|
|
|