Split some large files.
[git-p4: depot-paths = "//depot/solvespace/": change = 1777]
This commit is contained in:
parent
ab44c24cfc
commit
8a0809e6a0
6
Makefile
6
Makefile
|
@ -14,10 +14,14 @@ SSOBJS = $(OBJDIR)\solvespace.obj \
|
||||||
$(OBJDIR)\graphicswin.obj \
|
$(OBJDIR)\graphicswin.obj \
|
||||||
$(OBJDIR)\util.obj \
|
$(OBJDIR)\util.obj \
|
||||||
$(OBJDIR)\entity.obj \
|
$(OBJDIR)\entity.obj \
|
||||||
$(OBJDIR)\sketch.obj \
|
$(OBJDIR)\drawentity.obj \
|
||||||
|
$(OBJDIR)\group.obj \
|
||||||
|
$(OBJDIR)\groupmesh.obj \
|
||||||
|
$(OBJDIR)\request.obj \
|
||||||
$(OBJDIR)\glhelper.obj \
|
$(OBJDIR)\glhelper.obj \
|
||||||
$(OBJDIR)\expr.obj \
|
$(OBJDIR)\expr.obj \
|
||||||
$(OBJDIR)\constraint.obj \
|
$(OBJDIR)\constraint.obj \
|
||||||
|
$(OBJDIR)\draw.obj \
|
||||||
$(OBJDIR)\drawconstraint.obj \
|
$(OBJDIR)\drawconstraint.obj \
|
||||||
$(OBJDIR)\file.obj \
|
$(OBJDIR)\file.obj \
|
||||||
$(OBJDIR)\undoredo.obj \
|
$(OBJDIR)\undoredo.obj \
|
||||||
|
|
851
draw.cpp
Normal file
851
draw.cpp
Normal file
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
364
drawentity.cpp
Normal file
364
drawentity.cpp
Normal file
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
365
entity.cpp
365
entity.cpp
|
@ -1,5 +1,8 @@
|
||||||
#include "solvespace.h"
|
#include "solvespace.h"
|
||||||
|
|
||||||
|
const hEntity Entity::FREE_IN_3D = { 0 };
|
||||||
|
const hEntity Entity::NO_ENTITY = { 0 };
|
||||||
|
|
||||||
char *Entity::DescriptionString(void) {
|
char *Entity::DescriptionString(void) {
|
||||||
if(h.isFromRequest()) {
|
if(h.isFromRequest()) {
|
||||||
Request *r = SS.GetRequest(h.request());
|
Request *r = SS.GetRequest(h.request());
|
||||||
|
@ -616,368 +619,6 @@ ExprVector Entity::FaceGetPointExprs(void) {
|
||||||
return r;
|
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<Equation,hEquation> *l, Expr *expr, int index) {
|
void Entity::AddEq(IdList<Equation,hEquation> *l, Expr *expr, int index) {
|
||||||
Equation eq;
|
Equation eq;
|
||||||
eq.e = expr;
|
eq.e = expr;
|
||||||
|
|
845
graphicswin.cpp
845
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) {
|
void GraphicsWindow::ClearSuper(void) {
|
||||||
HideGraphicsEditControl();
|
HideGraphicsEditControl();
|
||||||
ClearPending();
|
ClearPending();
|
||||||
|
@ -743,459 +441,6 @@ void GraphicsWindow::ClearSuper(void) {
|
||||||
EnsureValidActives();
|
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) {
|
void GraphicsWindow::ToggleBool(int link, DWORD v) {
|
||||||
bool *vb = (bool *)v;
|
bool *vb = (bool *)v;
|
||||||
*vb = !*vb;
|
*vb = !*vb;
|
||||||
|
@ -1209,93 +454,3 @@ void GraphicsWindow::ToggleBool(int link, DWORD v) {
|
||||||
SS.later.showTW = true;
|
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,9 @@
|
||||||
#include "solvespace.h"
|
#include "solvespace.h"
|
||||||
|
|
||||||
const hEntity Entity::FREE_IN_3D = { 0 };
|
|
||||||
const hEntity Entity::NO_ENTITY = { 0 };
|
|
||||||
const hParam Param::NO_PARAM = { 0 };
|
const hParam Param::NO_PARAM = { 0 };
|
||||||
#define NO_PARAM (Param::NO_PARAM)
|
#define NO_PARAM (Param::NO_PARAM)
|
||||||
|
|
||||||
const hGroup Group::HGROUP_REFERENCES = { 1 };
|
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)
|
#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> *param, hParam hp) {
|
|
||||||
Param pa;
|
|
||||||
memset(&pa, 0, sizeof(pa));
|
|
||||||
pa.h = hp;
|
|
||||||
param->Add(&pa);
|
|
||||||
return hp;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Request::Generate(IdList<Entity,hEntity> *entity,
|
|
||||||
IdList<Param,hParam> *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;
|
|
||||||
}
|
|
||||||
|
|
244
groupmesh.cpp
Normal file
244
groupmesh.cpp
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
165
request.cpp
Normal file
165
request.cpp
Normal file
|
@ -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,hEntity> *entity,
|
||||||
|
IdList<Param,hParam> *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> *param, hParam hp) {
|
||||||
|
Param pa;
|
||||||
|
memset(&pa, 0, sizeof(pa));
|
||||||
|
pa.h = hp;
|
||||||
|
param->Add(&pa);
|
||||||
|
return hp;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user