solvespace/groupmesh.cpp
Jonathan Westhues b2f2f90a27 Add DXF export. The complexity comes from all the different ways to
specify the plane from which we want to grab the triangles. Shared
edges are then removed with the same code used to check for
watertight meshes, and the remaining edges are assembled into
polygons.

[git-p4: depot-paths = "//depot/solvespace/": change = 1823]
2008-07-07 22:30:13 -08:00

647 lines
22 KiB
C++

#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.how = POLY_GOOD;
poly.normal = poly.ComputeNormal();
poly.FixContourDirections();
if(!poly.AllPointsInPlane(&(polyError.notCoplanarAt))) {
// The edges aren't all coplanar; so not a good polygon
polyError.how = POLY_NOT_COPLANAR;
poly.Clear();
}
} else {
polyError.how = POLY_NOT_CLOSED;
polyError.notClosedAt = error;
poly.Clear();
}
edges.Clear();
}
}
void Group::GetTrajectory(hGroup hg, SContour *traj, SPolygon *section) {
if(section->IsEmpty()) return;
SEdgeList edges; ZERO(&edges);
int i, j;
for(i = 0; i < SS.entity.n; i++) {
Entity *e = &(SS.entity.elem[i]);
if(e->group.v != hg.v) continue;
e->GenerateEdges(&edges);
}
Vector pn = (section->normal).WithMagnitude(1);
double pd = pn.Dot(section->AnyPoint());
// Find the start of the trajectory
Vector first, last;
for(i = 0; i < edges.l.n; i++) {
SEdge *se = &(edges.l.elem[i]);
bool startA = true, startB = true;
for(j = 0; j < edges.l.n; j++) {
if(i == j) continue;
SEdge *set = &(edges.l.elem[j]);
if((set->a).Equals(se->a)) startA = false;
if((set->b).Equals(se->a)) startA = false;
if((set->a).Equals(se->b)) startB = false;
if((set->b).Equals(se->b)) startB = false;
}
if(startA || startB) {
// It's possible for both to be true, if only one segment exists
if(startA) {
first = se->a;
last = se->b;
} else {
first = se->b;
last = se->a;
}
se->tag = 1;
break;
}
}
if(i >= edges.l.n) goto cleanup;
edges.AssembleContour(first, last, traj, NULL);
if(traj->l.n < 1) goto cleanup;
// Starting and ending points of the trajectory
Vector ps, pf;
ps = traj->l.elem[0].p;
pf = traj->l.elem[traj->l.n - 1].p;
// Distances of those points to the section plane
double ds = fabs(pn.Dot(ps) - pd), df = fabs(pn.Dot(pf) - pd);
if(ds < LENGTH_EPS && df < LENGTH_EPS) {
if(section->WindingNumberForPoint(pf) > 0) {
// Both the start and finish lie on the section plane; let the
// start be the one that's somewhere within the section. Use
// winding > 0, not odd/even, since it's natural e.g. to sweep
// a ring to make a pipe, and draw the trajectory through the
// center of the ring.
traj->Reverse();
}
} else if(ds > df) {
// The starting point is the endpoint that's closer to the plane
traj->Reverse();
}
cleanup:
edges.Clear();
}
void Group::AddQuadWithNormal(STriMeta meta, Vector out,
Vector a, Vector b, Vector c, Vector d)
{
// The quad becomes two triangles
STriangle quad1 = STriangle::From(meta, a, b, c),
quad2 = STriangle::From(meta, c, d, a);
// Could be only one of the triangles has area; be sure
// to use that one for normal checking, then.
Vector n1 = quad1.Normal(), n2 = quad2.Normal();
Vector n = (n1.Magnitude() > n2.Magnitude()) ? n1 : n2;
if(n.Dot(out) < 0) {
quad1.FlipNormal();
quad2.FlipNormal();
}
// One or both of the endpoints might lie on the axis of
// rotation, in which case its triangle is zero-area.
if(n1.Magnitude() > LENGTH_EPS) thisMesh.AddTriangle(&quad1);
if(n2.Magnitude() > LENGTH_EPS) thisMesh.AddTriangle(&quad2);
}
void Group::GenerateMeshForStepAndRepeat(void) {
Group *src = SS.GetGroup(opA);
SMesh *srcm = &(src->thisMesh); // the mesh to step and repeat
if(srcm->l.n == 0) {
runningMesh.Clear();
runningMesh.MakeFromCopy(PreviousGroupMesh());
return;
}
SMesh origm;
ZERO(&origm);
origm.MakeFromCopy(src->PreviousGroupMesh());
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;
thisMesh.Clear();
if(type == TRANSLATE) {
Vector trans = Vector::From(h.param(0), h.param(1), h.param(2));
trans = trans.ScaledBy(ap);
for(int i = 0; i < srcm->l.n; i++) {
STriangle tr = srcm->l.elem[i];
tr.a = (tr.a).Plus(trans);
tr.b = (tr.b).Plus(trans);
tr.c = (tr.c).Plus(trans);
if(tr.meta.face != 0) {
hEntity he = { tr.meta.face };
tr.meta.face = Remap(he, remap).v;
}
thisMesh.AddTriangle(&tr);
}
} else {
Vector trans = Vector::From(h.param(0), h.param(1), h.param(2));
double theta = ap * SS.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);
for(int i = 0; i < srcm->l.n; i++) {
STriangle tr = srcm->l.elem[i];
tr.a = (q.Rotate((tr.a).Minus(trans))).Plus(trans);
tr.b = (q.Rotate((tr.b).Minus(trans))).Plus(trans);
tr.c = (q.Rotate((tr.c).Minus(trans))).Plus(trans);
if(tr.meta.face != 0) {
hEntity he = { tr.meta.face };
tr.meta.face = Remap(he, remap).v;
}
thisMesh.AddTriangle(&tr);
}
}
runningMesh.Clear();
if(src->meshCombine == COMBINE_AS_DIFFERENCE) {
runningMesh.MakeFromDifference(&origm, &thisMesh);
} else {
runningMesh.MakeFromUnion(&origm, &thisMesh);
}
origm.Clear();
origm.MakeFromCopy(&runningMesh);
}
origm.Clear();
thisMesh.Clear();
}
void Group::GenerateMeshForSweep(bool helical,
Vector axisp, Vector axis, Vector onHelix)
{
STriMeta meta = { 0, color };
int a, i;
// The closed section that will be swept along the curve
Group *section = SS.GetGroup(helical ? opA : opB);
SEdgeList edges;
ZERO(&edges);
(section->poly).MakeEdgesInto(&edges);
// The trajectory along which the section will be swept
SContour traj;
ZERO(&traj);
if(helical) {
double r0 = onHelix.DistanceToLine(axisp, axis);
int n = (int)(SS.CircleSides(r0)*valA) + 4;
Vector origin = onHelix.ClosestPointOnLine(axisp, axis);
Vector u = (onHelix.Minus(origin)).WithMagnitude(1);
Vector v = (axis.Cross(u)).WithMagnitude(1);
for(i = 0; i <= n; i++) {
double turns = (i*valA)/n;
double theta = turns*2*PI;
double r = r0 + turns*valC;
if(subtype == LEFT_HANDED) theta = -theta;
Vector p = origin.Plus(
u.ScaledBy(r*cos(theta)).Plus(
v.ScaledBy(r*sin(theta)).Plus(
axis.WithMagnitude(turns*valB))));
traj.AddPoint(p);
}
} else {
GetTrajectory(opA, &traj, &(section->poly));
}
if(traj.l.n <= 0) {
edges.Clear();
return; // no trajectory, nothing to do
}
// Initial offset/orientation determined by first pwl in trajectory
Vector origRef = traj.l.elem[0].p;
Vector origNormal = (traj.l.elem[1].p).Minus(origRef);
origNormal = origNormal.WithMagnitude(1);
Vector oldRef = origRef, oldNormal = origNormal;
Vector oldU, oldV;
if(helical) {
oldU = axis.WithMagnitude(1);
oldV = (oldNormal.Cross(oldU)).WithMagnitude(1);
// numerical fixup, since pwl segment isn't exactly tangent...
oldU = (oldV.Cross(oldNormal)).WithMagnitude(1);
} else {
oldU = oldNormal.Normal(0);
oldV = oldNormal.Normal(1);
}
// The endcap at the start of the curve
SPolygon cap;
ZERO(&cap);
edges.l.ClearTags();
edges.AssemblePolygon(&cap, NULL);
cap.normal = cap.ComputeNormal();
if(oldNormal.Dot(cap.normal) > 0) {
cap.normal = (cap.normal).ScaledBy(-1);
}
cap.TriangulateInto(&thisMesh, meta);
cap.Clear();
// Rewrite the source polygon so that the trajectory is along the
// z axis, and the poly lies in the xy plane
for(i = 0; i < edges.l.n; i++) {
SEdge *e = &(edges.l.elem[i]);
e->a = ((e->a).Minus(oldRef)).DotInToCsys(oldU, oldV, oldNormal);
e->b = ((e->b).Minus(oldRef)).DotInToCsys(oldU, oldV, oldNormal);
}
Vector polyn =
(section->poly.normal).DotInToCsys(oldU, oldV, oldNormal);
for(a = 1; a < traj.l.n; a++) {
Vector thisRef = traj.l.elem[a].p;
Vector thisNormal, useNormal;
if(a == traj.l.n - 1) {
thisNormal = oldNormal;
useNormal = oldNormal;
} else {
thisNormal = (traj.l.elem[a+1].p).Minus(thisRef);
useNormal = (thisNormal.Plus(oldNormal)).ScaledBy(0.5);
}
Vector useV, useU;
useNormal = useNormal.WithMagnitude(1);
if(helical) {
// The axis of rotation is always a basis vector
useU = axis.WithMagnitude(1);
useV = (useNormal.Cross(useU)).WithMagnitude(1);
} else {
// Choose a new coordinate system, normal to the trajectory and
// with the minimum possible twist about the normal.
useV = (useNormal.Cross(oldU)).WithMagnitude(1);
useU = (useV.Cross(useNormal)).WithMagnitude(1);
}
Quaternion qi = Quaternion::From(oldU, oldV);
Quaternion qf = Quaternion::From(useU, useV);
for(i = 0; i < edges.l.n; i++) {
SEdge *edge = &(edges.l.elem[i]);
Vector ai, bi, af, bf;
ai = qi.Rotate(edge->a).Plus(oldRef);
bi = qi.Rotate(edge->b).Plus(oldRef);
af = qf.Rotate(edge->a).Plus(thisRef);
bf = qf.Rotate(edge->b).Plus(thisRef);
Vector ab = (edge->b).Minus(edge->a);
Vector out = polyn.Cross(ab);
out = qf.Rotate(out);
AddQuadWithNormal(meta, out, ai, bi, bf, af);
}
oldRef = thisRef;
oldNormal = thisNormal;
oldU = useU;
oldV = useV;
}
Quaternion q = Quaternion::From(oldU, oldV);
for(i = 0; i < edges.l.n; i++) {
SEdge *edge = &(edges.l.elem[i]);
(edge->a) = q.Rotate(edge->a).Plus(oldRef);
(edge->b) = q.Rotate(edge->b).Plus(oldRef);
}
edges.l.ClearTags();
edges.AssemblePolygon(&cap, NULL);
cap.normal = cap.ComputeNormal();
if(oldNormal.Dot(cap.normal) < 0) {
cap.normal = (cap.normal).ScaledBy(-1);
}
cap.TriangulateInto(&thisMesh, meta);
cap.Clear();
traj.l.Clear();
edges.Clear();
}
void Group::GenerateMesh(void) {
thisMesh.Clear();
STriMeta meta = { 0, color };
if(type == TRANSLATE || type == ROTATE) {
GenerateMeshForStepAndRepeat();
goto done;
}
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);
// 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) {
thisMesh.AddTriangle(meta, ct, bt, at);
} else {
thisMesh.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) {
thisMesh.AddTriangle(meta, at, bt, ct);
} else {
thisMesh.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) {
thisMesh.AddTriangle(meta, bbot, abot, atop);
thisMesh.AddTriangle(meta, bbot, atop, btop);
} else {
thisMesh.AddTriangle(meta, abot, bbot, atop);
thisMesh.AddTriangle(meta, bbot, btop, atop);
}
}
edges.Clear();
} else if(type == LATHE) {
SEdgeList edges;
ZERO(&edges);
int a, i;
Group *src = SS.GetGroup(opA);
(src->poly).MakeEdgesInto(&edges);
Vector orig = SS.GetEntity(predef.origin)->PointGetNum();
Vector axis = SS.GetEntity(predef.entityB)->VectorGetNum();
axis = axis.WithMagnitude(1);
// Calculate the max radius, to determine fineness of mesh
double r, rmax = 0;
for(i = 0; i < edges.l.n; i++) {
SEdge *edge = &(edges.l.elem[i]);
r = (edge->a).DistanceToLine(orig, axis);
rmax = max(r, rmax);
r = (edge->b).DistanceToLine(orig, axis);
rmax = max(r, rmax);
}
int n = SS.CircleSides(rmax);
for(a = 0; a < n; a++) {
double thetai = (2*PI*WRAP(a-1, n))/n, thetaf = (2*PI*a)/n;
for(i = 0; i < edges.l.n; i++) {
SEdge *edge = &(edges.l.elem[i]);
Vector ai = (edge->a).RotatedAbout(orig, axis, thetai);
Vector bi = (edge->b).RotatedAbout(orig, axis, thetai);
Vector af = (edge->a).RotatedAbout(orig, axis, thetaf);
Vector bf = (edge->b).RotatedAbout(orig, axis, thetaf);
Vector ab = (edge->b).Minus(edge->a);
Vector out = ((src->poly).normal).Cross(ab);
// This is a vector, not a point, so no origin for rotation
out = out.RotatedAbout(axis, thetai);
AddQuadWithNormal(meta, out, ai, bi, bf, af);
}
}
} else if(type == SWEEP) {
Vector zp = Vector::From(0, 0, 0);
GenerateMeshForSweep(false, zp, zp, zp);
} else if(type == HELICAL_SWEEP) {
Entity *ln = SS.GetEntity(predef.entityB);
Vector lna = SS.GetEntity(ln->point[0])->PointGetNum(),
lnb = SS.GetEntity(ln->point[1])->PointGetNum();
Vector onh = SS.GetEntity(predef.origin)->PointGetNum();
GenerateMeshForSweep(true, lna, lnb.Minus(lna), onh);
} 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);
thisMesh.AddTriangle(&st);
}
}
runningMesh.Clear();
// If this group contributes no new mesh, then our running mesh is the
// same as last time, no combining required.
if(thisMesh.l.n == 0) {
runningMesh.MakeFromCopy(PreviousGroupMesh());
goto done;
}
// So our group's mesh appears in thisMesh. Combine this with the previous
// group's mesh, using the requested operation.
bool prevMeshError = meshError.yes;
meshError.yes = false;
meshError.interferesAt.Clear();
SMesh *a = PreviousGroupMesh();
if(meshCombine == COMBINE_AS_UNION) {
runningMesh.MakeFromUnion(a, &thisMesh);
} else if(meshCombine == COMBINE_AS_DIFFERENCE) {
runningMesh.MakeFromDifference(a, &thisMesh);
} else {
if(!runningMesh.MakeFromInterferenceCheck(a, &thisMesh,
&(meshError.interferesAt)))
{
meshError.yes = true;
// And the list of failed triangles goes in meshError.interferesAt
}
}
if(prevMeshError != meshError.yes) {
// 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->MakeCertainEdgesInto(&emphEdges, true);
}
}
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].runningMesh);
}
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 == DRAWING_3D || type == 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 && 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;
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
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(&runningMesh);
// And finally show the polygons too
if(!SS.GW.showShaded) return;
if(polyError.how == POLY_NOT_CLOSED) {
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);
glPushMatrix();
glxTranslatev(polyError.notClosedAt.b);
glxOntoWorkplane(SS.GW.projRight, SS.GW.projUp);
glxWriteText("not closed contour!");
glPopMatrix();
glEnable(GL_DEPTH_TEST);
} else if(polyError.how == POLY_NOT_COPLANAR) {
glDisable(GL_DEPTH_TEST);
glxColor3d(1, 0, 0);
glPushMatrix();
glxTranslatev(polyError.notCoplanarAt);
glxOntoWorkplane(SS.GW.projRight, SS.GW.projUp);
glxWriteText("points not all coplanar!");
glPopMatrix();
glEnable(GL_DEPTH_TEST);
} else {
glxColor4d(0, 0.1, 0.1, 0.5);
glxDepthRangeOffset(1);
glxFillPolygon(&poly);
glxDepthRangeOffset(0);
}
}