#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); } }