From cad77c9c47896841383fb06cbe4f1992c79277b1 Mon Sep 17 00:00:00 2001 From: Jonathan Westhues Date: Sat, 5 Jul 2008 23:56:24 -0800 Subject: [PATCH] Add routines to remove T intersections from the mesh, which are introduced by the bsp routines. It's usually, though not always, possible to generate a watertight mesh. The occasions where it's not look ugly, floating point issues, no quick fix. And use those to generate a list of edges where two different faces meet, which I can emphasize for cosmetic reasons (and some UI to specify whether to do that, and with what color). And make the right mouse button rotate the model, since that was previously doing nothing. [git-p4: depot-paths = "//depot/solvespace/": change = 1821] --- bsp.cpp | 27 +++- draw.cpp | 6 +- drawconstraint.cpp | 1 + dsc.h | 4 + glhelper.cpp | 27 ++-- groupmesh.cpp | 22 ++- mesh.cpp | 335 +++++++++++++++++++++++++++++++++++++++++++++ polygon.h | 23 +++- sketch.h | 1 + solvespace.cpp | 7 +- solvespace.h | 4 +- textscreens.cpp | 26 +++- ui.h | 12 +- undoredo.cpp | 2 + util.cpp | 40 +++++- win32/w32main.cpp | 8 +- wishlist.txt | 4 +- 17 files changed, 499 insertions(+), 50 deletions(-) diff --git a/bsp.cpp b/bsp.cpp index 177ea296..adfeab4d 100644 --- a/bsp.cpp +++ b/bsp.cpp @@ -3,6 +3,22 @@ SBsp2 *SBsp2::Alloc(void) { return (SBsp2 *)AllocTemporary(sizeof(SBsp2)); } SBsp3 *SBsp3::Alloc(void) { return (SBsp3 *)AllocTemporary(sizeof(SBsp3)); } +static int ByArea(const void *av, const void *bv) { + STriangle *a = (STriangle *)av; + STriangle *b = (STriangle *)bv; + + double fa = a->Normal().Magnitude(); + double fb = b->Normal().Magnitude(); + + if(fa > fb) { + return -1; + } else if(fa < fb) { + return 1; + } else { + return 0; + } +} + SBsp3 *SBsp3::FromMesh(SMesh *m) { SBsp3 *bsp3 = NULL; int i; @@ -12,13 +28,10 @@ SBsp3 *SBsp3::FromMesh(SMesh *m) { mc.AddTriangle(&(m->l.elem[i])); } - srand(0); // Let's be deterministic, at least! - int n = mc.l.n; - while(n > 1) { - int k = rand() % n; - n--; - SWAP(STriangle, mc.l.elem[k], mc.l.elem[n]); - } + // The larger-area triangles have more trustworthy normals. By inserting + // those first, we hope to improve the numerical accuracy of our + // split planes. + qsort(mc.l.elem, mc.l.n, sizeof(mc.l.elem[0]), ByArea); for(i = 0; i < mc.l.n; i++) { bsp3 = bsp3->Insert(&(mc.l.elem[i]), NULL); diff --git a/draw.cpp b/draw.cpp index 3eec33a4..5b567e4d 100644 --- a/draw.cpp +++ b/draw.cpp @@ -20,6 +20,10 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, bool middleDown, bool rightDown, bool shiftDown, bool ctrlDown) { if(GraphicsEditControlIsVisible()) return; + if(rightDown) { + middleDown = true; + shiftDown = true; + } Point2d mp = { x, y }; @@ -262,7 +266,7 @@ void GraphicsWindow::ClearPending(void) { memset(&pending, 0, sizeof(pending)); } -void GraphicsWindow::MouseMiddleDown(double x, double y) { +void GraphicsWindow::MouseMiddleOrRightDown(double x, double y) { if(GraphicsEditControlIsVisible()) return; orig.offset = offset; diff --git a/drawconstraint.cpp b/drawconstraint.cpp index 1803f5c6..a5f20cf3 100644 --- a/drawconstraint.cpp +++ b/drawconstraint.cpp @@ -77,6 +77,7 @@ void Constraint::DoLabel(Vector ref, Vector *labelPos, Vector gr, Vector gu) { glPopMatrix(); } else { double l = swidth/2 - sheight/2; + l = max(l, 5/SS.GW.scale); Point2d a = SS.GW.ProjectPoint(ref.Minus(gr.WithMagnitude(l))); Point2d b = SS.GW.ProjectPoint(ref.Plus (gr.WithMagnitude(l))); double d = dogd.mp.DistanceToLine(a, b.Minus(a), true); diff --git a/dsc.h b/dsc.h index 787ca399..cde82c68 100644 --- a/dsc.h +++ b/dsc.h @@ -46,7 +46,9 @@ public: static Vector AtIntersectionOfPlanes(Vector n1, double d1, Vector n2, double d2); + double Element(int i); bool Equals(Vector v); + bool EqualsExactly(Vector v); Vector Plus(Vector b); Vector Minus(Vector b); Vector Negated(void); @@ -58,8 +60,10 @@ public: Vector DotInToCsys(Vector u, Vector v, Vector n); Vector ScaleOutOfCsys(Vector u, Vector v, Vector n); double DistanceToLine(Vector p0, Vector dp); + bool OnLineSegment(Vector a, Vector b); Vector ClosestPointOnLine(Vector p0, Vector dp); double Magnitude(void); + double MagSquared(void); Vector WithMagnitude(double s); Vector ScaledBy(double s); Vector ProjectInto(hEntity wrkpl); diff --git a/glhelper.cpp b/glhelper.cpp index 86d84daa..9c381b07 100644 --- a/glhelper.cpp +++ b/glhelper.cpp @@ -280,29 +280,20 @@ void glxDebugPolygon(SPolygon *p) } } -void glxDebugEdgeList(SEdgeList *el) +void glxDrawEdges(SEdgeList *el) { int i; - glLineWidth(2); - glPointSize(7); - glDisable(GL_DEPTH_TEST); + glLineWidth(1); + glxDepthRangeOffset(2); + glxColor3d(REDf(SS.edgeColor), GREENf(SS.edgeColor), BLUEf(SS.edgeColor)); + + glBegin(GL_LINES); for(i = 0; i < el->l.n; i++) { SEdge *se = &(el->l.elem[i]); - if(se->tag) continue; - Vector a = se->a, b = se->b; - - glxLockColorTo(0, 1, 1); - Vector d = (a.Minus(b)).WithMagnitude(-0); - glBegin(GL_LINES); - glxVertex3v(a.Plus(d)); - glxVertex3v(b.Minus(d)); - glEnd(); - glxLockColorTo(0, 0, 1); - glBegin(GL_POINTS); - glxVertex3v(a.Plus(d)); - glxVertex3v(b.Minus(d)); - glEnd(); + glxVertex3v(se->a); + glxVertex3v(se->b); } + glEnd(); } void glxDebugMesh(SMesh *m) diff --git a/groupmesh.cpp b/groupmesh.cpp index 88f05586..505b7436 100644 --- a/groupmesh.cpp +++ b/groupmesh.cpp @@ -350,7 +350,7 @@ void Group::GenerateMesh(void) { if(type == TRANSLATE || type == ROTATE) { GenerateMeshForStepAndRepeat(); - return; + goto done; } if(type == EXTRUDE) { @@ -511,7 +511,7 @@ void Group::GenerateMesh(void) { // same as last time, no combining required. if(thisMesh.l.n == 0) { runningMesh.MakeFromCopy(PreviousGroupMesh()); - return; + goto done; } // So our group's mesh appears in thisMesh. Combine this with the previous @@ -536,6 +536,14 @@ void Group::GenerateMesh(void) { // The error is reported in the text window for the group. SS.later.showTW = true; } + +done: + emphEdges.Clear(); + if(h.v == SS.GW.activeGroup.v && SS.edgeColor != 0) { + SKdNode *root = SKdNode::From(&runningMesh); + root->SnapToMesh(&runningMesh); + root->MakeEdgesToEmphasizeInto(&emphEdges); + } } SMesh *Group::PreviousGroupMesh(void) { @@ -574,9 +582,13 @@ void Group::Draw(void) { 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, &runningMesh, mh, ms1, ms2); - glDisable(GL_LIGHTING); + if(SS.GW.showShaded) { + glEnable(GL_LIGHTING); + glxFillMesh(specColor, &runningMesh, mh, ms1, ms2); + glDisable(GL_LIGHTING); + + glxDrawEdges(&emphEdges); + } if(meshError.yes) { // Draw the error triangles in bright red stripes, with no Z buffering diff --git a/mesh.cpp b/mesh.cpp index b41e05a1..3488dae5 100644 --- a/mesh.cpp +++ b/mesh.cpp @@ -293,3 +293,338 @@ DWORD SMesh::FirstIntersectionWith(Point2d mp) { return face; } +#define KDTREE_EPS (20*LENGTH_EPS) // nice and sloppy + +STriangleLl *STriangleLl::Alloc(void) + { return (STriangleLl *)AllocTemporary(sizeof(STriangleLl)); } +SKdNode *SKdNode::Alloc(void) + { return (SKdNode *)AllocTemporary(sizeof(SKdNode)); } + +SKdNode *SKdNode::From(SMesh *m) { + int i; + STriangle *tra = (STriangle *)AllocTemporary((m->l.n) * sizeof(*tra)); + + for(i = 0; i < m->l.n; i++) { + tra[i] = m->l.elem[i]; + } + + srand(0); + int n = m->l.n; + while(n > 1) { + int k = rand() % n; + n--; + SWAP(STriangle, tra[k], tra[n]); + } + + STriangleLl *tll = NULL; + for(i = 0; i < m->l.n; i++) { + STriangleLl *tn = STriangleLl::Alloc(); + tn->tri = &(tra[i]); + tn->next = tll; + tll = tn; + } + + return SKdNode::From(tll, 0); +} + +SKdNode *SKdNode::From(STriangleLl *tll, int which) { + SKdNode *ret = Alloc(); + + if(!tll) goto leaf; + + int i; + int gtc[3] = { 0, 0, 0 }, ltc[3] = { 0, 0, 0 }, allc = 0; + double badness[3]; + double split[3]; + for(i = 0; i < 3; i++) { + int tcnt = 0; + STriangleLl *ll; + for(ll = tll; ll; ll = ll->next) { + STriangle *tr = ll->tri; + split[i] += (ll->tri->a).Element(i); + split[i] += (ll->tri->b).Element(i); + split[i] += (ll->tri->c).Element(i); + tcnt++; + } + split[i] /= (tcnt*3); + + for(ll = tll; ll; ll = ll->next) { + STriangle *tr = ll->tri; + + double a = (tr->a).Element(i), + b = (tr->b).Element(i), + c = (tr->c).Element(i); + + if(a < split[i] + KDTREE_EPS || + b < split[i] + KDTREE_EPS || + c < split[i] + KDTREE_EPS) + { + ltc[i]++; + } + if(a > split[i] - KDTREE_EPS || + b > split[i] - KDTREE_EPS || + c > split[i] - KDTREE_EPS) + { + gtc[i]++; + } + if(i == 0) allc++; + } + badness[i] = pow((double)ltc[i], 4) + pow((double)gtc[i], 4); + } + if(badness[0] < badness[1] && badness[0] < badness[2]) { + which = 0; + } else if(badness[1] < badness[2]) { + which = 1; + } else { + which = 2; + } + + if(allc < 10) goto leaf; + if(allc == gtc[which] || allc == ltc[which]) goto leaf; + + STriangleLl *ll; + STriangleLl *lgt = NULL, *llt = NULL; + for(ll = tll; ll; ll = ll->next) { + STriangle *tr = ll->tri; + + double a = (tr->a).Element(which), + b = (tr->b).Element(which), + c = (tr->c).Element(which); + + if(a < split[which] + KDTREE_EPS || + b < split[which] + KDTREE_EPS || + c < split[which] + KDTREE_EPS) + { + STriangleLl *n = STriangleLl::Alloc(); + *n = *ll; + n->next = llt; + llt = n; + } + if(a > split[which] - KDTREE_EPS || + b > split[which] - KDTREE_EPS || + c > split[which] - KDTREE_EPS) + { + STriangleLl *n = STriangleLl::Alloc(); + *n = *ll; + n->next = lgt; + lgt = n; + } + } + + ret->which = which; + ret->c = split[which]; + ret->gt = SKdNode::From(lgt, (which + 1) % 3); + ret->lt = SKdNode::From(llt, (which + 1) % 3); + return ret; + +leaf: +// dbp("leaf: allc=%d gtc=%d ltc=%d which=%d", allc, gtc[which], ltc[which], which); + ret->tris = tll; + return ret; +} + +void SKdNode::ClearTags(void) { + if(gt && lt) { + gt->ClearTags(); + lt->ClearTags(); + } else { + STriangleLl *ll; + for(ll = tris; ll; ll = ll->next) { + ll->tri->tag = 0; + } + } +} + +void SKdNode::AddTriangle(STriangle *tr) { + if(gt && lt) { + double ta = (tr->a).Element(which), + tb = (tr->b).Element(which), + tc = (tr->c).Element(which); + if(ta < c + KDTREE_EPS || + tb < c + KDTREE_EPS || + tc < c + KDTREE_EPS) + { + lt->AddTriangle(tr); + } + if(ta > c - KDTREE_EPS || + tb > c - KDTREE_EPS || + tc > c - KDTREE_EPS) + { + gt->AddTriangle(tr); + } + } else { + STriangleLl *tn = STriangleLl::Alloc(); + tn->tri = tr; + tn->next = tris; + tris = tn; + } +} + +void SKdNode::MakeMeshInto(SMesh *m) { + if(gt) gt->MakeMeshInto(m); + if(lt) lt->MakeMeshInto(m); + + STriangleLl *ll; + for(ll = tris; ll; ll = ll->next) { + if(ll->tri->tag) continue; + + m->AddTriangle(ll->tri); + ll->tri->tag = 1; + } +} + +void SKdNode::SnapToVertex(Vector v, SMesh *extras) { + if(gt && lt) { + double vc = v.Element(which); + if(vc < c + KDTREE_EPS) { + lt->SnapToVertex(v, extras); + } + if(vc > c - KDTREE_EPS) { + gt->SnapToVertex(v, extras); + } + // Nothing bad happens if the triangle to be split appears in both + // branches; the first call will split the triangle, so that the + // second call will do nothing, because the modified triangle will + // already contain v + } else { + STriangleLl *ll; + for(ll = tris; ll; ll = ll->next) { + STriangle *tr = ll->tri; + + // Do a cheap bbox test first + int k; + bool mightHit = true; + + for(k = 0; k < 3; k++) { + if((tr->a).Element(k) < v.Element(k) - KDTREE_EPS && + (tr->b).Element(k) < v.Element(k) - KDTREE_EPS && + (tr->c).Element(k) < v.Element(k) - KDTREE_EPS) + { + mightHit = false; + break; + } + if((tr->a).Element(k) > v.Element(k) + KDTREE_EPS && + (tr->b).Element(k) > v.Element(k) + KDTREE_EPS && + (tr->c).Element(k) > v.Element(k) + KDTREE_EPS) + { + mightHit = false; + break; + } + } + if(!mightHit) continue; + + if(tr->a.Equals(v)) { tr->a = v; continue; } + if(tr->b.Equals(v)) { tr->b = v; continue; } + if(tr->c.Equals(v)) { tr->c = v; continue; } + + if(v.OnLineSegment(tr->a, tr->b)) { + STriangle nt = STriangle::From(tr->meta, tr->a, v, tr->c); + extras->AddTriangle(&nt); + tr->a = v; + continue; + } + if(v.OnLineSegment(tr->b, tr->c)) { + STriangle nt = STriangle::From(tr->meta, tr->b, v, tr->a); + extras->AddTriangle(&nt); + tr->b = v; + continue; + } + if(v.OnLineSegment(tr->c, tr->a)) { + STriangle nt = STriangle::From(tr->meta, tr->c, v, tr->b); + extras->AddTriangle(&nt); + tr->c = v; + continue; + } + } + } +} + +void SKdNode::SnapToMesh(SMesh *m) { + int i, j, k; + for(i = 0; i < m->l.n; i++) { + STriangle *tr = &(m->l.elem[i]); + for(j = 0; j < 3; j++) { + Vector v = ((j == 0) ? tr->a : + ((j == 1) ? tr->b : + tr->c)); + + SMesh extra; + ZERO(&extra); + SnapToVertex(v, &extra); + + for(k = 0; k < extra.l.n; k++) { + STriangle *tra = (STriangle *)AllocTemporary(sizeof(*tra)); + *tra = extra.l.elem[k]; + AddTriangle(tra); + } + extra.Clear(); + } + } +} + +void SKdNode::FindEdgeOn(Vector a, Vector b, int *n, int *nOther, + STriMeta m, int cnt) +{ + if(gt && lt) { + double ac = a.Element(which), + bc = b.Element(which); + if(ac < c + KDTREE_EPS || + bc < c + KDTREE_EPS) + { + lt->FindEdgeOn(a, b, n, nOther, m, cnt); + } + if(ac > c - KDTREE_EPS || + bc > c - KDTREE_EPS) + { + gt->FindEdgeOn(a, b, n, nOther, m, cnt); + } + } else { + STriangleLl *ll; + for(ll = tris; ll; ll = ll->next) { + STriangle *tr = ll->tri; + + if(tr->tag == cnt) continue; + + if((a.EqualsExactly(tr->b) && b.EqualsExactly(tr->a)) || + (a.EqualsExactly(tr->c) && b.EqualsExactly(tr->b)) || + (a.EqualsExactly(tr->a) && b.EqualsExactly(tr->c))) + { + (*n)++; + if(tr->meta.face != m.face) (*nOther)++; + } + + tr->tag = cnt; + } + } +} + +void SKdNode::MakeEdgesToEmphasizeInto(SEdgeList *sel) { + SMesh m; + ZERO(&m); + ClearTags(); + MakeMeshInto(&m); + + int cnt = 1234; + int i, j; + for(i = 0; i < m.l.n; i++) { + STriangle *tr = &(m.l.elem[i]); + + for(j = 0; j < 3; j++) { + Vector a = (j == 0) ? tr->a : ((j == 1) ? tr->b : tr->c); + Vector b = (j == 0) ? tr->b : ((j == 1) ? tr->c : tr->a); + + int n = 0, nOther = 0; + FindEdgeOn(a, b, &n, &nOther, tr->meta, cnt++); + if(n != 1) { + dbp("hanging edge: n=%d (%.3f %.3f %.3f) (%.3f %.3f %.3f)", + n, CO(a), CO(b)); + } + if(nOther > 0) { + sel->AddEdge(a, b); + } + } + } + + m.Clear(); +} + diff --git a/polygon.h b/polygon.h index 54b84aaa..b9ac461f 100644 --- a/polygon.h +++ b/polygon.h @@ -217,9 +217,11 @@ public: STriangle *tri; STriangleLl *next; + + static STriangleLl *Alloc(void); }; -class SKdTree { +class SKdNode { public: static const int BY_X = 0; static const int BY_Y = 1; @@ -227,10 +229,25 @@ public: int which; double c; - SKdTree *gt; - SKdTree *lt; + SKdNode *gt; + SKdNode *lt; STriangleLl *tris; + + static SKdNode *Alloc(void); + static SKdNode *From(SMesh *m); + static SKdNode *From(STriangleLl *tll, int which); + + void AddTriangle(STriangle *tr); + void MakeMeshInto(SMesh *m); + void ClearTags(void); + + void FindEdgeOn(Vector a, Vector b, int *n, int *nOther, + STriMeta m, int cnt); + void MakeEdgesToEmphasizeInto(SEdgeList *sel); + + void SnapToMesh(SMesh *m); + void SnapToVertex(Vector v, SMesh *extras); }; #endif diff --git a/sketch.h b/sketch.h index d550b8ac..54012383 100644 --- a/sketch.h +++ b/sketch.h @@ -146,6 +146,7 @@ public: SMesh interferesAt; bool yes; } meshError; + SEdgeList emphEdges; static const int COMBINE_AS_UNION = 0; static const int COMBINE_AS_DIFFERENCE = 1; diff --git a/solvespace.cpp b/solvespace.cpp index 9848b012..8460be60 100644 --- a/solvespace.cpp +++ b/solvespace.cpp @@ -30,6 +30,8 @@ void SolveSpace::Init(char *cmdLine) { viewUnits = (Unit)CnfThawDWORD((DWORD)UNIT_MM, "ViewUnits"); // Camera tangent (determines perspective) cameraTangent = ((int)CnfThawDWORD(0, "CameraTangent"))/1e6; + // Color for edges (drawn as lines for emphasis) + edgeColor = CnfThawDWORD(RGB(0, 0, 0), "EdgeColor"); // Recent files menus for(i = 0; i < MAX_RECENT; i++) { char name[100]; @@ -82,6 +84,8 @@ void SolveSpace::Exit(void) { CnfFreezeDWORD((int)viewUnits, "ViewUnits"); // Camera tangent (determines perspective) CnfFreezeDWORD((int)(cameraTangent*1e6), "CameraTangent"); + // Color for edges (drawn as lines for emphasis) + CnfFreezeDWORD(edgeColor, "EdgeColor"); ExitNow(); } @@ -399,7 +403,8 @@ void SolveSpace::GenerateAll(int first, int last) { deleted.groups, deleted.groups == 1 ? "" : "s"); memset(&deleted, 0, sizeof(deleted)); } - + + FreeAllTemporary(); allConsistent = true; return; diff --git a/solvespace.h b/solvespace.h index ddeb869b..7d4c2d9c 100644 --- a/solvespace.h +++ b/solvespace.h @@ -126,7 +126,7 @@ void glxTesselatePolygon(GLUtesselator *gt, SPolygon *p); void glxFillPolygon(SPolygon *p); void glxFillMesh(int color, SMesh *m, DWORD h, DWORD s1, DWORD s2); void glxDebugPolygon(SPolygon *p); -void glxDebugEdgeList(SEdgeList *l); +void glxDrawEdges(SEdgeList *l); void glxDebugMesh(SMesh *m); void glxMarkPolygonNormal(SPolygon *p); void glxWriteText(char *str); @@ -348,6 +348,8 @@ public: double lightIntensity[2]; double meshTol; double cameraTangent; + DWORD edgeColor; + int CircleSides(double r); typedef enum { UNIT_MM = 0, diff --git a/textscreens.cpp b/textscreens.cpp index 6dcf8368..eba0c15e 100644 --- a/textscreens.cpp +++ b/textscreens.cpp @@ -553,6 +553,14 @@ void TextWindow::ScreenChangeCameraTangent(int link, DWORD v) { ShowTextEditControl(43, 3, str); SS.TW.edit.meaning = EDIT_CAMERA_TANGENT; } +void TextWindow::ScreenChangeEdgeColor(int link, DWORD v) { + char str[1024]; + sprintf(str, "%.3f, %.3f, %.3f", + REDf(SS.edgeColor), GREENf(SS.edgeColor), BLUEf(SS.edgeColor)); + + ShowTextEditControl(49, 3, str); + SS.TW.edit.meaning = EDIT_EDGE_COLOR; +} void TextWindow::ShowConfiguration(void) { int i; Printf(true, "%Ft material color-(r, g, b)"); @@ -583,13 +591,19 @@ void TextWindow::ShowConfiguration(void) { Printf(false, "%Ba %2 %Fl%Ll%f%D[change]%E; now %d triangles", SS.meshTol, &ScreenChangeMeshTolerance, 0, - SS.group.elem[SS.group.n-1].runningMesh.l.n); + SS.GetGroup(SS.GW.activeGroup)->runningMesh.l.n); Printf(false, ""); Printf(false, "%Ft perspective factor (0 for isometric)%E"); Printf(false, "%Ba %3 %Fl%Ll%f%D[change]%E", SS.cameraTangent*1000, &ScreenChangeCameraTangent, 0); + + Printf(false, ""); + Printf(false, "%Ft edge color r,g,b (0,0,0 for no edges)%E"); + Printf(false, "%Ba %@, %@, %@ %Fl%Ll%f%D[change]%E", + REDf(SS.edgeColor), GREENf(SS.edgeColor), BLUEf(SS.edgeColor), + &ScreenChangeEdgeColor, 0); } //----------------------------------------------------------------------------- @@ -664,6 +678,16 @@ void TextWindow::EditControlDone(char *s) { InvalidateGraphics(); break; } + case EDIT_EDGE_COLOR: { + double r, g, b; + if(sscanf(s, "%lf, %lf, %lf", &r, &g, &b)==3) { + SS.edgeColor = RGB(r*255, g*255, b*255); + } else { + Error("Bad format: specify color as r, g, b"); + } + SS.GenerateAll(0, INT_MAX); + break; + } case EDIT_HELIX_TURNS: case EDIT_HELIX_PITCH: case EDIT_HELIX_DRADIUS: { diff --git a/ui.h b/ui.h index 0ee602e7..1009a581 100644 --- a/ui.h +++ b/ui.h @@ -64,10 +64,11 @@ public: static const int EDIT_COLOR = 5; static const int EDIT_MESH_TOLERANCE = 6; static const int EDIT_CAMERA_TANGENT = 7; - static const int EDIT_HELIX_TURNS = 8; - static const int EDIT_HELIX_PITCH = 9; - static const int EDIT_HELIX_DRADIUS = 10; - static const int EDIT_TTF_TEXT = 11; + static const int EDIT_EDGE_COLOR = 8; + static const int EDIT_HELIX_TURNS = 20; + static const int EDIT_HELIX_PITCH = 21; + static const int EDIT_HELIX_DRADIUS = 22; + static const int EDIT_TTF_TEXT = 23; struct { int meaning; int i; @@ -129,6 +130,7 @@ public: static void ScreenChangeColor(int link, DWORD v); static void ScreenChangeMeshTolerance(int link, DWORD v); static void ScreenChangeCameraTangent(int link, DWORD v); + static void ScreenChangeEdgeColor(int link, DWORD v); void EditControlDone(char *s); }; @@ -345,7 +347,7 @@ public: void MouseLeftDown(double x, double y); void MouseLeftUp(double x, double y); void MouseLeftDoubleClick(double x, double y); - void MouseMiddleDown(double x, double y); + void MouseMiddleOrRightDown(double x, double y); void MouseScroll(double x, double y, int delta); void EditControlDone(char *s); }; diff --git a/undoredo.cpp b/undoredo.cpp index 806bb691..45b72d23 100644 --- a/undoredo.cpp +++ b/undoredo.cpp @@ -52,6 +52,7 @@ void SolveSpace::PushFromCurrentOnto(UndoStack *uk) { ZERO(&(dest.thisMesh)); ZERO(&(dest.runningMesh)); ZERO(&(dest.meshError)); + ZERO(&(dest.emphEdges)); ZERO(&(dest.remap)); src->remap.DeepCopyInto(&(dest.remap)); @@ -92,6 +93,7 @@ void SolveSpace::PopOntoCurrentFrom(UndoStack *uk) { g->thisMesh.Clear(); g->runningMesh.Clear(); g->meshError.interferesAt.Clear(); + g->emphEdges.Clear(); g->remap.Clear(); g->impMesh.Clear(); g->impEntity.Clear(); diff --git a/util.cpp b/util.cpp index 0fcaa1c4..cbfb2949 100644 --- a/util.cpp +++ b/util.cpp @@ -275,8 +275,28 @@ Vector Vector::From(hParam x, hParam y, hParam z) { return v; } +double Vector::Element(int i) { + switch(i) { + case 0: return x; + case 1: return y; + case 2: return z; + default: oops(); + } +} + bool Vector::Equals(Vector v) { - return (this->Minus(v)).Magnitude() < LENGTH_EPS; + // Quick axis-aligned tests before going further + double dx = v.x - x; if(dx < -LENGTH_EPS || dx > LENGTH_EPS) return false; + double dy = v.y - y; if(dy < -LENGTH_EPS || dy > LENGTH_EPS) return false; + double dz = v.z - z; if(dz < -LENGTH_EPS || dz > LENGTH_EPS) return false; + + return (this->Minus(v)).MagSquared() < LENGTH_EPS*LENGTH_EPS; +} + +bool Vector::EqualsExactly(Vector v) { + return (x == v.x) && + (y == v.y) && + (z == v.z); } Vector Vector::Plus(Vector b) { @@ -405,6 +425,20 @@ double Vector::DistanceToLine(Vector p0, Vector dp) { return ((this->Minus(p0)).Cross(dp)).Magnitude() / m; } +bool Vector::OnLineSegment(Vector a, Vector b) { + Vector d = b.Minus(a); + + double m = d.MagSquared(); + double distsq = ((this->Minus(a)).Cross(d)).MagSquared() / m; + + if(distsq >= LENGTH_EPS*LENGTH_EPS) return false; + + double t = (this->Minus(a)).DivPivoting(d); + // On-endpoint must be tested for separately. + if(t < 0 || t > 1) return false; + return true; +} + Vector Vector::ClosestPointOnLine(Vector p0, Vector dp) { dp = dp.WithMagnitude(1); // this, p0, and (p0+dp) define a plane; the min distance is in @@ -419,6 +453,10 @@ Vector Vector::ClosestPointOnLine(Vector p0, Vector dp) { return this->Plus(n.WithMagnitude(d)); } +double Vector::MagSquared(void) { + return x*x + y*y + z*z; +} + double Vector::Magnitude(void) { return sqrt(x*x + y*y + z*z); } diff --git a/win32/w32main.cpp b/win32/w32main.cpp index 18ca6edb..69492689 100644 --- a/win32/w32main.cpp +++ b/win32/w32main.cpp @@ -124,19 +124,16 @@ void *MemRealloc(void *p, int n) { } p = HeapReAlloc(Perm, HEAP_NO_SERIALIZE | HEAP_ZERO_MEMORY, p, n); - vl(); if(!p) oops(); return p; } void *MemAlloc(int n) { void *p = HeapAlloc(Perm, HEAP_NO_SERIALIZE | HEAP_ZERO_MEMORY, n); - vl(); if(!p) oops(); return p; } void MemFree(void *p) { HeapFree(Perm, HEAP_NO_SERIALIZE, p); - vl(); } void vl(void) { @@ -613,6 +610,7 @@ LRESULT CALLBACK GraphicsWndProc(HWND hwnd, UINT msg, WPARAM wParam, case WM_LBUTTONDOWN: case WM_LBUTTONUP: case WM_LBUTTONDBLCLK: + case WM_RBUTTONDOWN: case WM_MBUTTONDOWN: { int x = LOWORD(lParam); int y = HIWORD(lParam); @@ -631,8 +629,8 @@ LRESULT CALLBACK GraphicsWndProc(HWND hwnd, UINT msg, WPARAM wParam, SS.GW.MouseLeftUp(x, y); } else if(msg == WM_LBUTTONDBLCLK) { SS.GW.MouseLeftDoubleClick(x, y); - } else if(msg == WM_MBUTTONDOWN) { - SS.GW.MouseMiddleDown(x, y); + } else if(msg == WM_MBUTTONDOWN || msg == WM_RBUTTONDOWN) { + SS.GW.MouseMiddleOrRightDown(x, y); } else if(msg == WM_MOUSEMOVE) { SS.GW.MouseMoved(x, y, !!(wParam & MK_LBUTTON), diff --git a/wishlist.txt b/wishlist.txt index 21508c2b..4700ab90 100644 --- a/wishlist.txt +++ b/wishlist.txt @@ -1,5 +1,4 @@ -STL check for meshes, and T intersection removal STL export DXF export some kind of rounding / chamfer @@ -7,11 +6,12 @@ remove back button in browser? auto-generate circles and faces when lathing copy the section geometry to other end when sweeping cylindrical faces -draw explicit edges long term ----- incremental regen of entities? my own plane poly triangulation code +exact curved surfaces +