solvespace/groupmesh.cpp

491 lines
16 KiB
C++

#include "solvespace.h"
#define gs (SS.GW.gs)
bool Group::AssembleLoops(void) {
SBezierList sbl;
ZERO(&sbl);
int i;
for(i = 0; i < SK.entity.n; i++) {
Entity *e = &(SK.entity.elem[i]);
if(e->group.v != h.v) continue;
if(e->construction) continue;
if(e->forceHidden) continue;
e->GenerateBezierCurves(&sbl);
}
bool allClosed;
bezierLoopSet = SBezierLoopSet::From(&sbl, &poly,
&allClosed, &(polyError.notClosedAt));
sbl.Clear();
return allClosed;
}
void Group::GenerateLoops(void) {
poly.Clear();
bezierLoopSet.Clear();
if(type == DRAWING_3D || type == DRAWING_WORKPLANE ||
type == ROTATE || type == TRANSLATE || type == IMPORTED)
{
if(AssembleLoops()) {
polyError.how = POLY_GOOD;
if(!poly.AllPointsInPlane(&(polyError.errorPointAt))) {
// The edges aren't all coplanar; so not a good polygon
polyError.how = POLY_NOT_COPLANAR;
poly.Clear();
bezierLoopSet.Clear();
}
if(poly.SelfIntersecting(&(polyError.errorPointAt))) {
polyError.how = POLY_SELF_INTERSECTING;
poly.Clear();
bezierLoopSet.Clear();
}
} else {
polyError.how = POLY_NOT_CLOSED;
poly.Clear();
bezierLoopSet.Clear();
}
}
}
void SShell::RemapFaces(Group *g, int remap) {
SSurface *ss;
for(ss = surface.First(); ss; ss = surface.NextAfter(ss)){
hEntity face = { ss->face };
if(face.v == Entity::NO_ENTITY.v) continue;
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(&workB);
T *soFar = &workA, *scratch = &workB;
soFar->MakeFromCopyOf(prevs);
int n = (int)valA, a0 = 0;
if(subtype == ONE_SIDED && skipFirst) {
a0++; n++;
}
int a;
for(a = a0; a < n; a++) {
int ap = a*2 - (subtype == ONE_SIDED ? 0 : (n-1));
int remap = (a == (n - 1)) ? REMAP_LAST : a;
T transd;
ZERO(&transd);
if(type == TRANSLATE) {
Vector trans = Vector::From(h.param(0), h.param(1), h.param(2));
trans = trans.ScaledBy(ap);
transd.MakeFromTransformationOf(steps, trans, Quaternion::IDENTITY);
} else {
Vector trans = Vector::From(h.param(0), h.param(1), h.param(2));
double theta = ap * SK.GetParam(h.param(3))->val;
double c = cos(theta), s = sin(theta);
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);
// Rotation is centered at t; so A(x - t) + t = Ax + (t - At)
transd.MakeFromTransformationOf(steps,
trans.Minus(q.Rotate(trans)), q);
}
// We need to rewrite any plane face entities to the transformed ones.
transd.RemapFaces(this, remap);
if(how == COMBINE_AS_DIFFERENCE) {
scratch->MakeFromDifferenceOf(soFar, &transd);
} else if(how == COMBINE_AS_UNION) {
scratch->MakeFromUnionOf(soFar, &transd);
} else {
scratch->MakeFromAssemblyOf(soFar, &transd);
}
SWAP(T *, scratch, soFar);
scratch->Clear();
transd.Clear();
}
outs->Clear();
*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) {
bool prevBooleanFailed = booleanFailed;
booleanFailed = false;
thisShell.Clear();
thisMesh.Clear();
runningShell.Clear();
runningMesh.Clear();
if(type == TRANSLATE || type == ROTATE) {
Group *src = SK.GetGroup(opA);
Group *pg = src->PreviousGroup();
if(src->thisMesh.IsEmpty() && pg->runningMesh.IsEmpty() && !forceToMesh)
{
SShell *toStep = &(src->thisShell),
*prev = &(pg->runningShell);
// This isn't used, but it makes sure the display and calculation
// of our shell doesn't get optimized out because it looks like
// we contribute no solid model.
thisShell.MakeFromCopyOf(toStep);
GenerateForStepAndRepeat<SShell>
(prev, toStep, &runningShell, src->meshCombine);
if(meshCombine != COMBINE_AS_ASSEMBLE) {
runningShell.MergeCoincidentSurfaces();
}
} else {
SMesh prevm, stepm;
ZERO(&prevm);
ZERO(&stepm);
prevm.MakeFromCopyOf(&(pg->runningMesh));
pg->runningShell.TriangulateInto(&prevm);
// Setting thisMesh for same reasons as thisShell above.
thisMesh.MakeFromCopyOf(&prevm);
stepm.MakeFromCopyOf(&(src->thisMesh));
src->thisShell.TriangulateInto(&stepm);
SMesh outm;
ZERO(&outm);
GenerateForStepAndRepeat<SMesh>
(&prevm, &stepm, &outm, src->meshCombine);
// And make sure that the output mesh is vertex-to-vertex.
SKdNode *root = SKdNode::From(&outm);
root->SnapToMesh(&outm);
root->MakeMeshInto(&runningMesh);
outm.Clear();
stepm.Clear();
prevm.Clear();
}
displayDirty = true;
return;
}
if(type == EXTRUDE) {
Group *src = SK.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);
}
thisShell.MakeFromExtrusionOf(&(src->bezierLoopSet), tbot, ttop, color);
Vector onOrig = src->bezierLoopSet.point;
// And for any plane faces, annotate the model with the entity for
// that face, so that the user can select them with the mouse.
int i;
for(i = 0; i < thisShell.surface.n; i++) {
SSurface *ss = &(thisShell.surface.elem[i]);
hEntity face = Entity::NO_ENTITY;
Vector p = ss->PointAt(0, 0),
n = ss->NormalAt(0, 0).WithMagnitude(1);
double d = n.Dot(p);
if(i == 0 || i == 1) {
// These are the top and bottom of the shell.
if(fabs((onOrig.Plus(ttop)).Dot(n) - d) < LENGTH_EPS) {
face = Remap(Entity::NO_ENTITY, REMAP_TOP);
ss->face = face.v;
}
if(fabs((onOrig.Plus(tbot)).Dot(n) - d) < LENGTH_EPS) {
face = Remap(Entity::NO_ENTITY, REMAP_BOTTOM);
ss->face = face.v;
}
continue;
}
// So these are the sides
if(ss->degm != 1 || ss->degn != 1) continue;
Entity *e;
for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) {
if(e->group.v != opA.v) continue;
if(e->type != Entity::LINE_SEGMENT) continue;
Vector a = SK.GetEntity(e->point[0])->PointGetNum(),
b = SK.GetEntity(e->point[1])->PointGetNum();
a = a.Plus(ttop);
b = b.Plus(ttop);
// Could get taken backwards, so check all cases.
if((a.Equals(ss->ctrl[0][0]) && b.Equals(ss->ctrl[1][0])) ||
(b.Equals(ss->ctrl[0][0]) && a.Equals(ss->ctrl[1][0])) ||
(a.Equals(ss->ctrl[0][1]) && b.Equals(ss->ctrl[1][1])) ||
(b.Equals(ss->ctrl[0][1]) && a.Equals(ss->ctrl[1][1])))
{
face = Remap(e->h, REMAP_LINE_TO_FACE);
ss->face = face.v;
break;
}
}
}
} else if(type == LATHE) {
Group *src = SK.GetGroup(opA);
Vector pt = SK.GetEntity(predef.origin)->PointGetNum(),
axis = SK.GetEntity(predef.entityB)->VectorGetNum();
axis = axis.WithMagnitude(1);
thisShell.MakeFromRevolutionOf(&(src->bezierLoopSet), pt, axis, color);
} else if(type == IMPORTED) {
// The imported shell or mesh are copied over, with the appropriate
// transformation applied. We also must remap the face entities.
Vector offset = {
SK.GetParam(h.param(0))->val,
SK.GetParam(h.param(1))->val,
SK.GetParam(h.param(2))->val };
Quaternion q = {
SK.GetParam(h.param(3))->val,
SK.GetParam(h.param(4))->val,
SK.GetParam(h.param(5))->val,
SK.GetParam(h.param(6))->val };
thisMesh.MakeFromTransformationOf(&impMesh, offset, q);
thisMesh.RemapFaces(this, 0);
thisShell.MakeFromTransformationOf(&impShell, offset, q);
thisShell.RemapFaces(this, 0);
}
if(meshCombine != COMBINE_AS_ASSEMBLE) {
thisShell.MergeCoincidentSurfaces();
}
// 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.
Group *pg = PreviousGroup();
if(pg->runningMesh.IsEmpty() && thisMesh.IsEmpty() && !forceToMesh) {
SShell *prevs = &(pg->runningShell);
GenerateForBoolean<SShell>(prevs, &thisShell, &runningShell);
if(meshCombine != COMBINE_AS_ASSEMBLE) {
runningShell.MergeCoincidentSurfaces();
}
// If the Boolean failed, then we should note that in the text screen
// for this group.
booleanFailed = runningShell.booleanFailed;
if(booleanFailed != prevBooleanFailed) {
SS.later.showTW = true;
}
} else {
SMesh prevm, thism;
ZERO(&prevm);
ZERO(&thism);
prevm.MakeFromCopyOf(&(pg->runningMesh));
pg->runningShell.TriangulateInto(&prevm);
thism.MakeFromCopyOf(&thisMesh);
thisShell.TriangulateInto(&thism);
SMesh outm;
ZERO(&outm);
GenerateForBoolean<SMesh>(&prevm, &thism, &outm);
// And make sure that the output mesh is vertex-to-vertex.
SKdNode *root = SKdNode::From(&outm);
root->SnapToMesh(&outm);
root->MakeMeshInto(&runningMesh);
outm.Clear();
thism.Clear();
prevm.Clear();
}
displayDirty = true;
}
void Group::GenerateDisplayItems(void) {
// This is potentially slow (since we've got to triangulate a shell, or
// to find the emphasized edges for a mesh), so we will run it only
// if its inputs have changed.
if(displayDirty) {
displayMesh.Clear();
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();
if(SS.GW.showEdges) {
runningShell.MakeEdgesInto(&displayEdges);
runningMesh.MakeEmphasizedEdgesInto(&displayEdges);
}
displayDirty = false;
}
}
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) return NULL;
return &(SK.group.elem[i-1]);
}
void Group::DrawDisplayItems(int t) {
int specColor;
if(t == DRAWING_3D || t == DRAWING_WORKPLANE) {
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 && SK.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;
if(SS.GW.showShaded) {
glEnable(GL_LIGHTING);
glxFillMesh(specColor, &displayMesh, mh, ms1, ms2);
glDisable(GL_LIGHTING);
}
if(SS.GW.showEdges) {
glLineWidth(1);
glxDepthRangeOffset(2);
glxColor3d(REDf (SS.edgeColor),
GREENf(SS.edgeColor),
BLUEf (SS.edgeColor));
glxDrawEdges(&displayEdges, false);
}
if(SS.GW.showMesh) glxDebugMesh(&displayMesh);
}
void Group::Draw(void) {
// Everything here gets drawn whether or not the group is hidden; we
// can control this stuff independently, with show/hide solids, edges,
// mesh, etc.
Group *pg = PreviousGroup();
if(pg && thisMesh.IsEmpty() && thisShell.IsEmpty()) {
// We don't contribute any new solid model in this group, so our
// display items are identical to the previous group's; which means
// that we can just display those, and stop ourselves from
// recalculating for those every time we get a change in this group.
pg->GenerateDisplayItems();
pg->DrawDisplayItems(type);
} else {
GenerateDisplayItems();
DrawDisplayItems(type);
}
// And finally show the polygons too
if(!SS.GW.showShaded) return;
if(polyError.how == POLY_NOT_CLOSED) {
// Report this error only in sketch-in-workplane groups; otherwise
// it's just a nuisance.
if(type == DRAWING_WORKPLANE) {
glDisable(GL_DEPTH_TEST);
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);
glxWriteText("not closed contour!",
polyError.notClosedAt.b, SS.GW.projRight, SS.GW.projUp,
NULL, NULL);
glEnable(GL_DEPTH_TEST);
}
} else if(polyError.how == POLY_NOT_COPLANAR ||
polyError.how == POLY_SELF_INTERSECTING)
{
// These errors occur at points, not lines
if(type == DRAWING_WORKPLANE) {
glDisable(GL_DEPTH_TEST);
glxColor3d(1, 0, 0);
char *msg = (polyError.how == POLY_NOT_COPLANAR) ?
"points not all coplanar!" :
"contour is self-intersecting!";
glxWriteText(msg,
polyError.errorPointAt, SS.GW.projRight, SS.GW.projUp,
NULL, NULL);
glEnable(GL_DEPTH_TEST);
}
} else {
glxColor4d(0, 0.1, 0.1, 0.5);
glxDepthRangeOffset(1);
glxFillPolygon(&poly);
glxDepthRangeOffset(0);
}
}