From 03ecbad981ed6cefa8d126bce5e31152b3dba908 Mon Sep 17 00:00:00 2001 From: Jonathan Westhues Date: Fri, 22 May 2009 02:02:02 -0800 Subject: [PATCH] Add beginnings of stuff to represent surfaces as either meshes or exact surface shells. And add interference checking; I'll be lazy and just do that on the meshes, by modifying the self-intersection tester to ignore coplanar triangles (since that can happen in an assembly). [git-p4: depot-paths = "//depot/solvespace/": change = 1958] --- groupmesh.cpp | 10 ++-- mesh.cpp | 129 ++++++++++++++++++++++++++++++------------------ polygon.h | 9 ++-- sketch.h | 2 +- solvespace.cpp | 23 +++++++-- textscreens.cpp | 28 +++++++++++ ui.h | 1 + 7 files changed, 141 insertions(+), 61 deletions(-) diff --git a/groupmesh.cpp b/groupmesh.cpp index 19ce083..e51c39d 100644 --- a/groupmesh.cpp +++ b/groupmesh.cpp @@ -59,7 +59,7 @@ void Group::GenerateShellForStepAndRepeat(void) { ZERO(&workA); ZERO(&workB); SShell *soFar = &workA, *scratch = &workB; - soFar->MakeFromCopyOf(src->PreviousGroupShell()); + soFar->MakeFromCopyOf(&(src->PreviousGroup()->runningShell)); int n = (int)valA, a0 = 0; if(subtype == ONE_SIDED && skipFirst) { @@ -234,13 +234,13 @@ void Group::GenerateShellAndMesh(void) { // same as last time, no combining required. Likewise if we have a mesh // but it's suppressed. if(suppress) { - runningShell.MakeFromCopyOf(PreviousGroupShell()); + runningShell.MakeFromCopyOf(&(PreviousGroup()->runningShell)); goto done; } // So our group's shell appears in thisShell. Combine this with the // previous group's shell, using the requested operation. - SShell *a = PreviousGroupShell(); + SShell *a = &(PreviousGroup()->runningShell); if(meshCombine == COMBINE_AS_UNION) { runningShell.MakeFromUnionOf(a, &thisShell); } else if(meshCombine == COMBINE_AS_DIFFERENCE) { @@ -263,14 +263,14 @@ void Group::GenerateDisplayItems(void) { } } -SShell *Group::PreviousGroupShell(void) { +Group *Group::PreviousGroup(void) { int i; for(i = 0; i < SK.group.n; i++) { Group *g = &(SK.group.elem[i]); if(g->h.v == h.v) break; } if(i == 0 || i >= SK.group.n) oops(); - return &(SK.group.elem[i-1].runningShell); + return &(SK.group.elem[i-1]); } void Group::Draw(void) { diff --git a/mesh.cpp b/mesh.cpp index 1babdbf..e1380b4 100644 --- a/mesh.cpp +++ b/mesh.cpp @@ -477,7 +477,7 @@ void SKdNode::MakeMeshInto(SMesh *m) { } void SKdNode::FindEdgeOn(Vector a, Vector b, int *n, int cnt, - bool *inter, bool *fwd) + bool coplanarIsInter, bool *inter, bool *fwd) { if(gt && lt) { double ac = a.Element(which), @@ -485,61 +485,86 @@ void SKdNode::FindEdgeOn(Vector a, Vector b, int *n, int cnt, if(ac < c + KDTREE_EPS || bc < c + KDTREE_EPS) { - lt->FindEdgeOn(a, b, n, cnt, inter, fwd); + lt->FindEdgeOn(a, b, n, cnt, coplanarIsInter, inter, fwd); } if(ac > c - KDTREE_EPS || bc > c - KDTREE_EPS) { - gt->FindEdgeOn(a, b, n, cnt, inter, fwd); + gt->FindEdgeOn(a, b, n, cnt, coplanarIsInter, inter, fwd); } - } else { - STriangleLl *ll; - for(ll = tris; ll; ll = ll->next) { - STriangle *tr = ll->tri; + return; + } + + // We are a leaf node; so we iterate over all the triangles in our + // linked list. + STriangleLl *ll; + for(ll = tris; ll; ll = ll->next) { + STriangle *tr = ll->tri; - if(tr->tag == cnt) continue; - - // Test if this triangle matches up with the given edge - if((a.Equals(tr->b) && b.Equals(tr->a)) || - (a.Equals(tr->c) && b.Equals(tr->b)) || - (a.Equals(tr->a) && b.Equals(tr->c))) - { - (*n)++; - // Record whether this triangle is front- or back-facing. - if(tr->Normal().z > LENGTH_EPS) { - *fwd = true; - } else { - *fwd = false; - } - } else if(((a.Equals(tr->a) && b.Equals(tr->b)) || - (a.Equals(tr->b) && b.Equals(tr->c)) || - (a.Equals(tr->c) && b.Equals(tr->a)))) - { - // It's an edge of this triangle, okay. + if(tr->tag == cnt) continue; + + // Test if this triangle matches up with the given edge + if((a.Equals(tr->b) && b.Equals(tr->a)) || + (a.Equals(tr->c) && b.Equals(tr->b)) || + (a.Equals(tr->a) && b.Equals(tr->c))) + { + (*n)++; + // Record whether this triangle is front- or back-facing. + if(tr->Normal().z > LENGTH_EPS) { + *fwd = true; } else { - // Check for self-intersection - Vector n = (tr->Normal()).WithMagnitude(1); - double d = (tr->a).Dot(n); - double pa = a.Dot(n) - d, pb = b.Dot(n) - d; - // It's an intersection if neither point lies in-plane, - // and the edge crosses the plane (should handle in-plane - // intersections separately but don't yet). - if((pa < -LENGTH_EPS || pa > LENGTH_EPS) && - (pb < -LENGTH_EPS || pb > LENGTH_EPS) && - (pa*pb < 0)) - { - // The edge crosses the plane of the triangle; now see if - // it crosses inside the triangle. - if(tr->ContainsPointProjd(b.Minus(a), a)) { + *fwd = false; + } + } else if(((a.Equals(tr->a) && b.Equals(tr->b)) || + (a.Equals(tr->b) && b.Equals(tr->c)) || + (a.Equals(tr->c) && b.Equals(tr->a)))) + { + // It's an edge of this triangle, okay. + } else { + // Check for self-intersection + Vector n = (tr->Normal()).WithMagnitude(1); + double d = (tr->a).Dot(n); + double pa = a.Dot(n) - d, pb = b.Dot(n) - d; + // It's an intersection if neither point lies in-plane, + // and the edge crosses the plane (should handle in-plane + // intersections separately but don't yet). + if((pa < -LENGTH_EPS || pa > LENGTH_EPS) && + (pb < -LENGTH_EPS || pb > LENGTH_EPS) && + (pa*pb < 0)) + { + // The edge crosses the plane of the triangle; now see if + // it crosses inside the triangle. + if(tr->ContainsPointProjd(b.Minus(a), a)) { + if(coplanarIsInter) { *inter = true; + } else { + Vector p = Vector::AtIntersectionOfPlaneAndLine( + n, d, a, b, NULL); + Vector ta = tr->a, + tb = tr->b, + tc = tr->c; + if((p.DistanceToLine(ta, tb.Minus(ta)) < LENGTH_EPS) || + (p.DistanceToLine(tb, tc.Minus(tb)) < LENGTH_EPS) || + (p.DistanceToLine(tc, ta.Minus(tc)) < LENGTH_EPS)) + { + // Intersection lies on edge. This happens when + // our edge is from a triangle coplanar with + // another triangle in the mesh. We don't test + // the edge against triangles whose plane contains + // that edge, but we do end up testing against + // the coplanar triangle's neighbours, which we + // will intersect on their edges. + } else { + *inter = true; + } } } } - - // Ensure that we don't count this triangle twice if it appears - // in two buckets of the kd tree. - tr->tag = cnt; } + + // Ensure that we don't count this triangle twice if it appears + // in two buckets of the kd tree. + tr->tag = cnt; } } @@ -679,7 +704,17 @@ void SKdNode::OcclusionTestLine(SEdge orig, SEdgeList *sel, int cnt) { } } -void SKdNode::MakeNakedEdgesInto(SEdgeList *sel, bool *inter, bool *leaky) { +//----------------------------------------------------------------------------- +// Report all naked edges of the mesh (i.e., edges that don't join up to +// a single anti-parallel edge of another triangle), and all edges that +// intersect another triangle. If coplanarIsInter, then edges coplanar with +// another triangle and within it are reported, otherwise not. We report +// in *inter and *leaky whether the mesh is self-intersecting or leaky +// (having naked edges) respectively. +//----------------------------------------------------------------------------- +void SKdNode::MakeNakedEdgesInto(SEdgeList *sel, bool coplanarIsInter, + bool *inter, bool *leaky) +{ if(inter) *inter = false; if(leaky) *leaky = false; @@ -699,7 +734,7 @@ void SKdNode::MakeNakedEdgesInto(SEdgeList *sel, bool *inter, bool *leaky) { int n = 0, nOther = 0; bool thisIntersects = false, fwd; - FindEdgeOn(a, b, &n, cnt, &thisIntersects, &fwd); + FindEdgeOn(a, b, &n, cnt, coplanarIsInter, &thisIntersects, &fwd); if(n != 1) { sel->AddEdge(a, b); if(leaky) *leaky = true; @@ -735,7 +770,7 @@ void SKdNode::MakeTurningEdgesInto(SEdgeList *sel) { int n = 0; bool inter, fwd; - FindEdgeOn(a, b, &n, cnt, &inter, &fwd); + FindEdgeOn(a, b, &n, cnt, true, &inter, &fwd); if(n == 1) { // and its neighbour is front-facing, so generate the edge. if(fwd) sel->AddEdge(a, b); diff --git a/polygon.h b/polygon.h index 60d8c51..183edff 100644 --- a/polygon.h +++ b/polygon.h @@ -223,10 +223,11 @@ public: void AddTriangle(STriangle *tr); void MakeMeshInto(SMesh *m); void ClearTags(void); - - void FindEdgeOn(Vector a, Vector b, int *n, int cnt, - bool *inter, bool *fwd); - void MakeNakedEdgesInto(SEdgeList *sel, bool *inter=NULL, bool *leaky=NULL); + + void FindEdgeOn(Vector a, Vector b, int *n, int cnt, bool coplanarIsInter, + bool *inter, bool *fwd); + void MakeNakedEdgesInto(SEdgeList *sel, bool coplanarIsInter, + bool *inter, bool *leaky); void MakeTurningEdgesInto(SEdgeList *sel); void OcclusionTestLine(SEdge orig, SEdgeList *sel, int cnt); diff --git a/sketch.h b/sketch.h index 8670c2f..88ff6a7 100644 --- a/sketch.h +++ b/sketch.h @@ -207,7 +207,7 @@ public: bool AssembleLoops(void); void GenerateLoops(void); // And the mesh stuff - SShell *PreviousGroupShell(void); + Group *PreviousGroup(void); void GenerateShellForStepAndRepeat(void); void GenerateDisplayItems(void); void GenerateShellAndMesh(void); diff --git a/solvespace.cpp b/solvespace.cpp index 89a778a..149de4e 100644 --- a/solvespace.cpp +++ b/solvespace.cpp @@ -420,7 +420,7 @@ void SolveSpace::MenuAnalyze(int id) { SMesh *m = &(SK.GetGroup(SS.GW.activeGroup)->displayMesh); SKdNode *root = SKdNode::From(m); bool inters, leaks; - root->MakeNakedEdgesInto(&(SS.nakedEdges), &inters, &leaks); + root->MakeNakedEdgesInto(&(SS.nakedEdges), true, &inters, &leaks); InvalidateGraphics(); char *intersMsg = inters ? @@ -440,6 +440,24 @@ void SolveSpace::MenuAnalyze(int id) { break; } + case GraphicsWindow::MNU_INTERFERENCE: { + SS.nakedEdges.Clear(); + + SMesh *m = &(SK.GetGroup(SS.GW.activeGroup)->displayMesh); + SKdNode *root = SKdNode::From(m); + bool inters, leaks; + root->MakeNakedEdgesInto(&(SS.nakedEdges), false, &inters, &leaks); + InvalidateGraphics(); + + if(inters) { + Error("%d edges interfere with other triangles, bad.", + SS.nakedEdges.l.n); + } else { + Message("The assembly does not interfere, good."); + } + break; + } + case GraphicsWindow::MNU_VOLUME: { SMesh *m = &(SK.GetGroup(SS.GW.activeGroup)->displayMesh); @@ -501,9 +519,6 @@ void SolveSpace::MenuAnalyze(int id) { break; } - case GraphicsWindow::MNU_INTERFERENCE: - break; - case GraphicsWindow::MNU_SHOW_DOF: // This works like a normal solve, except that it calculates // which variables are free/bound at the same time. diff --git a/textscreens.cpp b/textscreens.cpp index 0276a72..2144b3f 100644 --- a/textscreens.cpp +++ b/textscreens.cpp @@ -227,6 +227,15 @@ void TextWindow::ScreenChangeMeshCombine(int link, DWORD v) { SS.GenerateAll(); SS.GW.ClearSuper(); } +void TextWindow::ScreenChangeMeshOrExact(int link, DWORD v) { + SS.UndoRemember(); + + Group *g = SK.GetGroup(SS.TW.shown.group); + g->forceToMesh = !(g->forceToMesh); + SS.MarkGroupDirty(g->h); + SS.GenerateAll(); + SS.GW.ClearSuper(); +} void TextWindow::ScreenChangeSuppress(int link, DWORD v) { SS.UndoRemember(); @@ -449,6 +458,25 @@ void TextWindow::ShowGroupInfo(void) { 0x80000000 | SS.modelColor[7], 7, &TextWindow::ScreenColor); } + if(shown.group.v != Group::HGROUP_REFERENCES.v && + (g->runningMesh.l.n > 0 || + g->runningShell.surface.n > 0)) + { + Group *pg = g->PreviousGroup(); + if(pg->runningMesh.l.n == 0 && g->thisMesh.l.n == 0) { + bool fm = g->forceToMesh; + Printf(true, + "%FtSURFACES%E %Fh%f%Ll%s%E%Fs%s%E / %Fh%f%Ll%s%E%Fs%s%E", + &TextWindow::ScreenChangeMeshOrExact, + (!fm ? "" : "as NURBS"), (!fm ? "as NURBS" : ""), + &TextWindow::ScreenChangeMeshOrExact, + (fm ? "" : "as mesh"), (fm ? "as mesh" : "")); + } else { + Printf(false, + "%FtSURFACES%E %Fas mesh%FE"); + } + } + // Leave more space if the group has configuration stuff above the req/ // constraint list (as all but the drawing groups do). if(g->type == Group::DRAWING_3D || g->type == Group::DRAWING_WORKPLANE) { diff --git a/ui.h b/ui.h index 90583e8..fa0ebaa 100644 --- a/ui.h +++ b/ui.h @@ -136,6 +136,7 @@ public: static void ScreenChangeOneOrTwoSides(int link, DWORD v); static void ScreenChangeSkipFirst(int link, DWORD v); static void ScreenChangeMeshCombine(int link, DWORD v); + static void ScreenChangeMeshOrExact(int link, DWORD v); static void ScreenChangeSuppress(int link, DWORD v); static void ScreenChangeRightLeftHanded(int link, DWORD v); static void ScreenChangeHelixParameter(int link, DWORD v);