Add ability to represent our surfaces as either a shell or a mesh,

according to the user's preference. I templated the housekeeping
stuff for Boolean operations and step and repeat, so it's
relatively clean.

Still need to add the stuff to make a mesh vertex-to-vertex, and to
export sections of a mesh.

[git-p4: depot-paths = "//depot/solvespace/": change = 1959]
This commit is contained in:
Jonathan Westhues 2009-05-24 03:37:07 -08:00
parent 03ecbad981
commit ddbd0ff77b
11 changed files with 183 additions and 113 deletions

View File

@ -1026,7 +1026,7 @@ void GraphicsWindow::Paint(int w, int h) {
// And the naked edges, if the user did Analyze -> Show Naked Edges. // And the naked edges, if the user did Analyze -> Show Naked Edges.
glLineWidth(7); glLineWidth(7);
glColor3d(1, 0, 0); glColor3d(1, 0, 0);
glxDrawEdges(&(SS.nakedEdges)); glxDrawEdges(&(SS.nakedEdges), true);
// Then redraw whatever the mouse is hovering over, highlighted. // Then redraw whatever the mouse is hovering over, highlighted.
glDisable(GL_DEPTH_TEST); glDisable(GL_DEPTH_TEST);

View File

@ -7,7 +7,7 @@ void SolveSpace::ExportSectionTo(char *filename) {
Group *g = SK.GetGroup(SS.GW.activeGroup); Group *g = SK.GetGroup(SS.GW.activeGroup);
g->GenerateDisplayItems(); g->GenerateDisplayItems();
if(g->displayMesh.l.n == 0) { if(g->displayMesh.IsEmpty()) {
Error("No solid model present; draw one with extrudes and revolves, " Error("No solid model present; draw one with extrudes and revolves, "
"or use Export 2d View to export bare lines and curves."); "or use Export 2d View to export bare lines and curves.");
return; return;
@ -100,7 +100,7 @@ void SolveSpace::ExportViewTo(char *filename) {
g->GenerateDisplayItems(); g->GenerateDisplayItems();
sm = &(g->displayMesh); sm = &(g->displayMesh);
} }
if(sm->l.n == 0) { if(sm->IsEmpty()) {
sm = NULL; sm = NULL;
} }
@ -979,7 +979,7 @@ void HpglFileWriter::FinishAndCloseFile(void) {
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void SolveSpace::ExportMeshTo(char *filename) { void SolveSpace::ExportMeshTo(char *filename) {
SMesh *m = &(SK.GetGroup(SS.GW.activeGroup)->displayMesh); SMesh *m = &(SK.GetGroup(SS.GW.activeGroup)->displayMesh);
if(m->l.n == 0) { if(m->IsEmpty()) {
Error("Active group mesh is empty; nothing to export."); Error("Active group mesh is empty; nothing to export.");
return; return;
} }

View File

@ -293,7 +293,7 @@ void glxDebugPolygon(SPolygon *p)
} }
} }
void glxDrawEdges(SEdgeList *el) void glxDrawEdges(SEdgeList *el, bool endpointsToo)
{ {
SEdge *se; SEdge *se;
glBegin(GL_LINES); glBegin(GL_LINES);
@ -302,6 +302,8 @@ void glxDrawEdges(SEdgeList *el)
glxVertex3v(se->b); glxVertex3v(se->b);
} }
glEnd(); glEnd();
if(endpointsToo) {
glPointSize(12); glPointSize(12);
glBegin(GL_POINTS); glBegin(GL_POINTS);
for(se = el->l.First(); se; se = el->l.NextAfter(se)) { for(se = el->l.First(); se; se = el->l.NextAfter(se)) {
@ -309,6 +311,7 @@ void glxDrawEdges(SEdgeList *el)
glxVertex3v(se->b); glxVertex3v(se->b);
} }
glEnd(); glEnd();
}
} }
void glxDebugMesh(SMesh *m) void glxDebugMesh(SMesh *m)

View File

@ -51,15 +51,35 @@ void Group::GenerateLoops(void) {
} }
} }
void Group::GenerateShellForStepAndRepeat(void) { void SShell::RemapFaces(Group *g, int remap) {
Group *src = SK.GetGroup(opA); SSurface *ss;
SShell *srcs = &(src->thisShell); // the shell to step and repeat for(ss = surface.First(); ss; ss = surface.NextAfter(ss)){
hEntity face = { ss->face };
if(face.v == Entity::NO_ENTITY.v) continue;
SShell workA, workB; face = g->Remap(face, remap);
ss->face = face.v;
}
}
void SMesh::RemapFaces(Group *g, int remap) {
STriangle *tr;
for(tr = l.First(); tr; tr = l.NextAfter(tr)) {
hEntity face = { tr->meta.face };
if(face.v == Entity::NO_ENTITY.v) continue;
face = g->Remap(face, remap);
tr->meta.face = face.v;
}
}
template<class T>
void Group::GenerateForStepAndRepeat(T *prevs, T *steps, T *outs, int how) {
T workA, workB;
ZERO(&workA); ZERO(&workA);
ZERO(&workB); ZERO(&workB);
SShell *soFar = &workA, *scratch = &workB; T *soFar = &workA, *scratch = &workB;
soFar->MakeFromCopyOf(&(src->PreviousGroup()->runningShell)); soFar->MakeFromCopyOf(prevs);
int n = (int)valA, a0 = 0; int n = (int)valA, a0 = 0;
if(subtype == ONE_SIDED && skipFirst) { if(subtype == ONE_SIDED && skipFirst) {
@ -70,13 +90,12 @@ void Group::GenerateShellForStepAndRepeat(void) {
int ap = a*2 - (subtype == ONE_SIDED ? 0 : (n-1)); int ap = a*2 - (subtype == ONE_SIDED ? 0 : (n-1));
int remap = (a == (n - 1)) ? REMAP_LAST : a; int remap = (a == (n - 1)) ? REMAP_LAST : a;
SShell transd; T transd;
ZERO(&transd); ZERO(&transd);
if(type == TRANSLATE) { if(type == TRANSLATE) {
Vector trans = Vector::From(h.param(0), h.param(1), h.param(2)); Vector trans = Vector::From(h.param(0), h.param(1), h.param(2));
trans = trans.ScaledBy(ap); trans = trans.ScaledBy(ap);
Quaternion q = Quaternion::From(1, 0, 0, 0); transd.MakeFromTransformationOf(steps, trans, Quaternion::IDENTITY);
transd.MakeFromTransformationOf(srcs, trans, q);
} else { } else {
Vector trans = Vector::From(h.param(0), h.param(1), h.param(2)); Vector trans = Vector::From(h.param(0), h.param(1), h.param(2));
double theta = ap * SK.GetParam(h.param(3))->val; double theta = ap * SK.GetParam(h.param(3))->val;
@ -84,43 +103,88 @@ void Group::GenerateShellForStepAndRepeat(void) {
Vector axis = Vector::From(h.param(4), h.param(5), h.param(6)); Vector axis = Vector::From(h.param(4), h.param(5), h.param(6));
Quaternion q = Quaternion::From(c, s*axis.x, s*axis.y, s*axis.z); Quaternion q = Quaternion::From(c, s*axis.x, s*axis.y, s*axis.z);
// Rotation is centered at t; so A(x - t) + t = Ax + (t - At) // Rotation is centered at t; so A(x - t) + t = Ax + (t - At)
transd.MakeFromTransformationOf(srcs, transd.MakeFromTransformationOf(steps,
trans.Minus(q.Rotate(trans)), q); trans.Minus(q.Rotate(trans)), q);
} }
// We need to rewrite any plane face entities to the transformed ones. // We need to rewrite any plane face entities to the transformed ones.
SSurface *ss; transd.RemapFaces(this, remap);
for(ss = transd.surface.First(); ss; ss = transd.surface.NextAfter(ss)){
hEntity face = { ss->face };
if(face.v == Entity::NO_ENTITY.v) continue;
face = Remap(face, remap); if(how == COMBINE_AS_DIFFERENCE) {
ss->face = face.v;
}
if(src->meshCombine == COMBINE_AS_DIFFERENCE) {
scratch->MakeFromDifferenceOf(soFar, &transd); scratch->MakeFromDifferenceOf(soFar, &transd);
} else if(src->meshCombine == COMBINE_AS_UNION) { } else if(how == COMBINE_AS_UNION) {
scratch->MakeFromUnionOf(soFar, &transd); scratch->MakeFromUnionOf(soFar, &transd);
} else { } else {
scratch->MakeFromAssemblyOf(soFar, &transd); scratch->MakeFromAssemblyOf(soFar, &transd);
} }
SWAP(SShell *, scratch, soFar); SWAP(T *, scratch, soFar);
scratch->Clear(); scratch->Clear();
transd.Clear(); transd.Clear();
} }
runningShell.Clear(); outs->Clear();
runningShell = *soFar; *outs = *soFar;
}
template<class T>
void Group::GenerateForBoolean(T *prevs, T *thiss, T *outs) {
// If this group contributes no new mesh, then our running mesh is the
// same as last time, no combining required. Likewise if we have a mesh
// but it's suppressed.
if(thiss->IsEmpty() || suppress) {
outs->MakeFromCopyOf(prevs);
return;
}
// So our group's shell appears in thisShell. Combine this with the
// previous group's shell, using the requested operation.
if(meshCombine == COMBINE_AS_UNION) {
outs->MakeFromUnionOf(prevs, thiss);
} else if(meshCombine == COMBINE_AS_DIFFERENCE) {
outs->MakeFromDifferenceOf(prevs, thiss);
} else {
outs->MakeFromAssemblyOf(prevs, thiss);
}
} }
void Group::GenerateShellAndMesh(void) { void Group::GenerateShellAndMesh(void) {
thisShell.Clear(); thisShell.Clear();
thisMesh.Clear();
runningShell.Clear();
runningMesh.Clear();
if(type == TRANSLATE || type == ROTATE) { if(type == TRANSLATE || type == ROTATE) {
GenerateShellForStepAndRepeat(); Group *src = SK.GetGroup(opA);
goto done; Group *pg = src->PreviousGroup();
if(src->thisMesh.IsEmpty() && pg->runningMesh.IsEmpty() && !forceToMesh)
{
SShell *toStep = &(src->thisShell),
*prev = &(pg->runningShell);
GenerateForStepAndRepeat<SShell>
(prev, toStep, &runningShell, src->meshCombine);
} else {
SMesh prevm, stepm;
ZERO(&prevm);
ZERO(&stepm);
prevm.MakeFromCopyOf(&(pg->runningMesh));
pg->runningShell.TriangulateInto(&prevm);
stepm.MakeFromCopyOf(&(src->thisMesh));
src->thisShell.TriangulateInto(&stepm);
GenerateForStepAndRepeat<SMesh>
(&prevm, &stepm, &runningMesh, src->meshCombine);
stepm.Clear();
prevm.Clear();
}
displayDirty = true;
return;
} }
if(type == EXTRUDE) { if(type == EXTRUDE) {
@ -205,51 +269,38 @@ void Group::GenerateShellAndMesh(void) {
SK.GetParam(h.param(5))->val, SK.GetParam(h.param(5))->val,
SK.GetParam(h.param(6))->val }; SK.GetParam(h.param(6))->val };
for(int i = 0; i < impMesh.l.n; i++) { thisMesh.MakeFromTransformationOf(&impMesh, offset, q);
STriangle st = impMesh.l.elem[i]; thisMesh.RemapFaces(this, 0);
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);
}
thisShell.MakeFromTransformationOf(&impShell, offset, q); thisShell.MakeFromTransformationOf(&impShell, offset, q);
SSurface *srf; thisShell.RemapFaces(this, 0);
IdList<SSurface,hSSurface> *sl = &(thisShell.surface);
for(srf = sl->First(); srf; srf = sl->NextAfter(srf)) {
if(srf->face != 0) {
hEntity he = { srf->face };
srf->face = Remap(he, 0).v;
}
}
} }
runningShell.Clear(); // So now we've got the mesh or shell for this group. Combine it with
// the previous group's mesh or shell with the requested Boolean, and
// we're done.
// If this group contributes no new mesh, then our running mesh is the Group *pg = PreviousGroup();
// same as last time, no combining required. Likewise if we have a mesh if(pg->runningMesh.IsEmpty() && thisMesh.IsEmpty() && !forceToMesh) {
// but it's suppressed. SShell *prevs = &(pg->runningShell);
if(suppress) { GenerateForBoolean<SShell>(prevs, &thisShell, &runningShell);
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 = &(PreviousGroup()->runningShell);
if(meshCombine == COMBINE_AS_UNION) {
runningShell.MakeFromUnionOf(a, &thisShell);
} else if(meshCombine == COMBINE_AS_DIFFERENCE) {
runningShell.MakeFromDifferenceOf(a, &thisShell);
} else { } else {
runningShell.MakeFromAssemblyOf(a, &thisShell); SMesh prevm, thism;
ZERO(&prevm);
ZERO(&thism);
prevm.MakeFromCopyOf(&(pg->runningMesh));
pg->runningShell.TriangulateInto(&prevm);
thism.MakeFromCopyOf(&thisMesh);
thisShell.TriangulateInto(&thism);
GenerateForBoolean<SMesh>(&prevm, &thism, &runningMesh);
thism.Clear();
prevm.Clear();
} }
done:
displayDirty = true; displayDirty = true;
} }
@ -257,8 +308,19 @@ void Group::GenerateDisplayItems(void) {
if(displayDirty) { if(displayDirty) {
displayMesh.Clear(); displayMesh.Clear();
runningShell.TriangulateInto(&displayMesh); runningShell.TriangulateInto(&displayMesh);
STriangle *tr;
for(tr = runningMesh.l.First(); tr; tr = runningMesh.l.NextAfter(tr)) {
STriangle trn = *tr;
Vector n = trn.Normal();
trn.an = n;
trn.bn = n;
trn.cn = n;
displayMesh.AddTriangle(&trn);
}
displayEdges.Clear(); displayEdges.Clear();
runningShell.MakeEdgesInto(&displayEdges); runningShell.MakeEdgesInto(&displayEdges);
displayDirty = false; displayDirty = false;
} }
} }
@ -314,7 +376,7 @@ void Group::Draw(void) {
glxColor3d(REDf (SS.edgeColor), glxColor3d(REDf (SS.edgeColor),
GREENf(SS.edgeColor), GREENf(SS.edgeColor),
BLUEf (SS.edgeColor)); BLUEf (SS.edgeColor));
glxDrawEdges(&displayEdges); glxDrawEdges(&displayEdges, false);
} }
if(SS.GW.showMesh) glxDebugMesh(&displayMesh); if(SS.GW.showMesh) glxDebugMesh(&displayMesh);

View File

@ -206,7 +206,7 @@ void SMesh::AddAgainstBsp(SMesh *srcm, SBsp3 *bsp3) {
} }
} }
void SMesh::MakeFromUnion(SMesh *a, SMesh *b) { void SMesh::MakeFromUnionOf(SMesh *a, SMesh *b) {
SBsp3 *bspa = SBsp3::FromMesh(a); SBsp3 *bspa = SBsp3::FromMesh(a);
SBsp3 *bspb = SBsp3::FromMesh(b); SBsp3 *bspb = SBsp3::FromMesh(b);
@ -219,7 +219,7 @@ void SMesh::MakeFromUnion(SMesh *a, SMesh *b) {
AddAgainstBsp(a, bspb); AddAgainstBsp(a, bspb);
} }
void SMesh::MakeFromDifference(SMesh *a, SMesh *b) { void SMesh::MakeFromDifferenceOf(SMesh *a, SMesh *b) {
SBsp3 *bspa = SBsp3::FromMesh(a); SBsp3 *bspa = SBsp3::FromMesh(a);
SBsp3 *bspb = SBsp3::FromMesh(b); SBsp3 *bspb = SBsp3::FromMesh(b);
@ -232,40 +232,33 @@ void SMesh::MakeFromDifference(SMesh *a, SMesh *b) {
AddAgainstBsp(a, bspb); AddAgainstBsp(a, bspb);
} }
bool SMesh::MakeFromInterferenceCheck(SMesh *srca, SMesh *srcb, SMesh *error) { void SMesh::MakeFromCopyOf(SMesh *a) {
SBsp3 *bspa = SBsp3::FromMesh(srca);
SBsp3 *bspb = SBsp3::FromMesh(srcb);
error->Clear();
error->flipNormal = true;
error->keepCoplanar = false;
error->AddAgainstBsp(srcb, bspa);
error->AddAgainstBsp(srca, bspb);
// Now we have a list of all the triangles (or fragments thereof) from
// A that lie inside B, or vice versa. That's the interference, and
// we report it so that it can be flagged.
// For the actual output, take the union.
flipNormal = false;
keepCoplanar = false;
AddAgainstBsp(srcb, bspa);
flipNormal = false;
keepCoplanar = true;
AddAgainstBsp(srca, bspb);
// And we're successful if the intersection was empty.
return (error->l.n == 0);
}
void SMesh::MakeFromCopy(SMesh *a) {
int i; int i;
for(i = 0; i < a->l.n; i++) { for(i = 0; i < a->l.n; i++) {
AddTriangle(&(a->l.elem[i])); AddTriangle(&(a->l.elem[i]));
} }
} }
void SMesh::MakeFromAssemblyOf(SMesh *a, SMesh *b) {
MakeFromCopyOf(a);
MakeFromCopyOf(b);
}
void SMesh::MakeFromTransformationOf(SMesh *a, Vector trans, Quaternion q) {
STriangle *tr;
for(tr = a->l.First(); tr; tr = a->l.NextAfter(tr)) {
STriangle tt = *tr;
tt.a = (q.Rotate(tt.a)).Plus(trans);
tt.b = (q.Rotate(tt.b)).Plus(trans);
tt.c = (q.Rotate(tt.c)).Plus(trans);
AddTriangle(&tt);
}
}
bool SMesh::IsEmpty(void) {
return (l.n == 0);
}
DWORD SMesh::FirstIntersectionWith(Point2d mp) { DWORD SMesh::FirstIntersectionWith(Point2d mp) {
Vector p0 = Vector::From(mp.x, mp.y, 0); Vector p0 = Vector::From(mp.x, mp.y, 0);
Vector gn = Vector::From(0, 0, 1); Vector gn = Vector::From(0, 0, 1);

View File

@ -184,10 +184,15 @@ public:
void Simplify(int start); void Simplify(int start);
void AddAgainstBsp(SMesh *srcm, SBsp3 *bsp3); void AddAgainstBsp(SMesh *srcm, SBsp3 *bsp3);
void MakeFromUnion(SMesh *a, SMesh *b); void MakeFromUnionOf(SMesh *a, SMesh *b);
void MakeFromDifference(SMesh *a, SMesh *b); void MakeFromDifferenceOf(SMesh *a, SMesh *b);
bool MakeFromInterferenceCheck(SMesh *srca, SMesh *srcb, SMesh *errorAt);
void MakeFromCopy(SMesh *a); void MakeFromCopyOf(SMesh *a);
void MakeFromTransformationOf(SMesh *a, Vector trans, Quaternion q);
void MakeFromAssemblyOf(SMesh *a, SMesh *b);
bool IsEmpty(void);
void RemapFaces(Group *g, int remap);
DWORD FirstIntersectionWith(Point2d mp); DWORD FirstIntersectionWith(Point2d mp);
}; };

View File

@ -208,9 +208,10 @@ public:
void GenerateLoops(void); void GenerateLoops(void);
// And the mesh stuff // And the mesh stuff
Group *PreviousGroup(void); Group *PreviousGroup(void);
void GenerateShellForStepAndRepeat(void);
void GenerateDisplayItems(void); void GenerateDisplayItems(void);
void GenerateShellAndMesh(void); void GenerateShellAndMesh(void);
template<class T> void GenerateForStepAndRepeat(T *a, T *b, T *o, int how);
template<class T> void GenerateForBoolean(T *a, T *b, T *o);
void Draw(void); void Draw(void);
SPolygon GetPolygon(void); SPolygon GetPolygon(void);

View File

@ -149,7 +149,7 @@ void vl(void); // debug function to validate heaps
// End of platform-specific functions // End of platform-specific functions
//================ //================
class Group;
class SSurface; class SSurface;
#include "dsc.h" #include "dsc.h"
#include "polygon.h" #include "polygon.h"
@ -175,7 +175,7 @@ void glxTesselatePolygon(GLUtesselator *gt, SPolygon *p);
void glxFillPolygon(SPolygon *p); void glxFillPolygon(SPolygon *p);
void glxFillMesh(int color, SMesh *m, DWORD h, DWORD s1, DWORD s2); void glxFillMesh(int color, SMesh *m, DWORD h, DWORD s1, DWORD s2);
void glxDebugPolygon(SPolygon *p); void glxDebugPolygon(SPolygon *p);
void glxDrawEdges(SEdgeList *l); void glxDrawEdges(SEdgeList *l, bool endpointsToo);
void glxDebugMesh(SMesh *m); void glxDebugMesh(SMesh *m);
void glxMarkPolygonNormal(SPolygon *p); void glxMarkPolygonNormal(SPolygon *p);
void glxWriteText(char *str); void glxWriteText(char *str);

View File

@ -666,6 +666,10 @@ void SShell::TriangulateInto(SMesh *sm) {
} }
} }
bool SShell::IsEmpty(void) {
return (surface.n == 0);
}
void SShell::Clear(void) { void SShell::Clear(void) {
SSurface *s; SSurface *s;
for(s = surface.First(); s; s = surface.NextAfter(s)) { for(s = surface.First(); s; s = surface.NextAfter(s)) {

View File

@ -319,6 +319,8 @@ public:
void MakeEdgesInto(SEdgeList *sel); void MakeEdgesInto(SEdgeList *sel);
void MakeSectionEdgesInto(Vector n, double d, void MakeSectionEdgesInto(Vector n, double d,
SEdgeList *sel, SBezierList *sbl); SEdgeList *sel, SBezierList *sbl);
bool IsEmpty(void);
void RemapFaces(Group *g, int remap);
void Clear(void); void Clear(void);
}; };

View File

@ -463,7 +463,7 @@ void TextWindow::ShowGroupInfo(void) {
g->runningShell.surface.n > 0)) g->runningShell.surface.n > 0))
{ {
Group *pg = g->PreviousGroup(); Group *pg = g->PreviousGroup();
if(pg->runningMesh.l.n == 0 && g->thisMesh.l.n == 0) { if(pg->runningMesh.IsEmpty() && g->thisMesh.IsEmpty()) {
bool fm = g->forceToMesh; bool fm = g->forceToMesh;
Printf(true, Printf(true,
"%FtSURFACES%E %Fh%f%Ll%s%E%Fs%s%E / %Fh%f%Ll%s%E%Fs%s%E", "%FtSURFACES%E %Fh%f%Ll%s%E%Fs%s%E / %Fh%f%Ll%s%E%Fs%s%E",
@ -473,7 +473,7 @@ void TextWindow::ShowGroupInfo(void) {
(fm ? "" : "as mesh"), (fm ? "as mesh" : "")); (fm ? "" : "as mesh"), (fm ? "as mesh" : ""));
} else { } else {
Printf(false, Printf(false,
"%FtSURFACES%E %Fas mesh%FE"); "%FtSURFACES%E %Fsas mesh%E");
} }
} }