diff --git a/Makefile b/Makefile index 313df30..81c9cc2 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,7 @@ DEFINES = /D_WIN32_WINNT=0x500 /DISOLATION_AWARE_ENABLED /D_WIN32_IE=0x500 /DWIN32_LEAN_AND_MEAN /DWIN32 -CFLAGS = /W3 /nologo -Iextlib -I..\common\win32 /D_DEBUG /D_CRT_SECURE_NO_WARNINGS /I. /Zi /EHs +# Use the multi-threaded static libc because libpng and zlib do; not sure if anything bad +# happens if those mix, but don't want to risk it. +CFLAGS = /W3 /nologo -MT -Iextlib -I..\common\win32 /D_DEBUG /D_CRT_SECURE_NO_WARNINGS /I. /Zi /EHs HEADERS = ..\common\win32\freeze.h ui.h solvespace.h dsc.h sketch.h expr.h polygon.h diff --git a/draw.cpp b/draw.cpp index 6bcaeed..e8b2bbe 100644 --- a/draw.cpp +++ b/draw.cpp @@ -804,7 +804,7 @@ void GraphicsWindow::HitTestMakeSelection(Point2d mp) { // Faces, from the triangle mesh; these are lowest priority if(s.constraint.v == 0 && s.entity.v == 0 && showShaded && showFaces) { - SMesh *m = &((SS.GetGroup(activeGroup))->mesh); + SMesh *m = &((SS.GetGroup(activeGroup))->runningMesh); DWORD v = m->FirstIntersectionWith(mp); if(v) { s.entity.v = v; diff --git a/dsc.h b/dsc.h index bb10b49..787ca39 100644 --- a/dsc.h +++ b/dsc.h @@ -55,6 +55,8 @@ public: Vector Normal(int which); Vector RotatedAbout(Vector orig, Vector axis, double theta); Vector RotatedAbout(Vector axis, double theta); + Vector DotInToCsys(Vector u, Vector v, Vector n); + Vector ScaleOutOfCsys(Vector u, Vector v, Vector n); double DistanceToLine(Vector p0, Vector dp); Vector ClosestPointOnLine(Vector p0, Vector dp); double Magnitude(void); diff --git a/file.cpp b/file.cpp index 98c3c0a..96bcc58 100644 --- a/file.cpp +++ b/file.cpp @@ -65,6 +65,7 @@ const SolveSpace::SaveTable SolveSpace::SAVED[] = { { 'g', "Group.name", 'N', &(SS.sv.g.name) }, { 'g', "Group.activeWorkplane.v", 'x', &(SS.sv.g.activeWorkplane.v) }, { 'g', "Group.opA.v", 'x', &(SS.sv.g.opA.v) }, + { 'g', "Group.opB.v", 'x', &(SS.sv.g.opB.v) }, { 'g', "Group.valA", 'f', &(SS.sv.g.valA) }, { 'g', "Group.color", 'x', &(SS.sv.g.color) }, { 'g', "Group.subtype", 'd', &(SS.sv.g.subtype) }, @@ -219,7 +220,7 @@ bool SolveSpace::SaveToFile(char *filename) { fprintf(fh, "AddConstraint\n\n"); } - SMesh *m = &(group.elem[group.n-1].mesh); + SMesh *m = &(group.elem[group.n-1].runningMesh); for(i = 0; i < m->l.n; i++) { STriangle *tr = &(m->l.elem[i]); fprintf(fh, "Triangle %08x %08x " diff --git a/graphicswin.cpp b/graphicswin.cpp index 1d9eb5d..55152fe 100644 --- a/graphicswin.cpp +++ b/graphicswin.cpp @@ -39,14 +39,15 @@ const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = { { 1, "Dimensions in &Millimeters", MNU_UNITS_MM, 0, mView }, { 0, "&New Group", 0, 0, NULL }, -{ 1, "&Drawing in 3d\tShift+Ctrl+D", MNU_GROUP_3D, 'D'|S|C, mGrp }, -{ 1, "Drawing in Workplane\tShift+Ctrl+W", MNU_GROUP_WRKPL, 'W'|S|C, mGrp }, +{ 1, "&Drawing in 3d\tShift+Ctrl+D", MNU_GROUP_3D, 'D'|S|C,mGrp }, +{ 1, "Drawing in Workplane\tShift+Ctrl+W", MNU_GROUP_WRKPL, 'W'|S|C,mGrp }, { 1, NULL, 0, NULL }, { 1, "Step &Translating\tShift+Ctrl+R", MNU_GROUP_TRANS, 'T'|S|C,mGrp }, { 1, "Step &Rotating\tShift+Ctrl+T", MNU_GROUP_ROT, 'R'|S|C,mGrp }, { 1, NULL, 0, 0, NULL }, -{ 1, "Extrude\tShift+Ctrl+X", MNU_GROUP_EXTRUDE, 'X'|S|C,mGrp }, -{ 1, "Lathe\tShift+Ctrl+L", MNU_GROUP_LATHE, 'L'|S|C,mGrp }, +{ 1, "E&xtrude\tShift+Ctrl+X", MNU_GROUP_EXTRUDE, 'X'|S|C,mGrp }, +{ 1, "&Lathe\tShift+Ctrl+L", MNU_GROUP_LATHE, 'L'|S|C,mGrp }, +{ 1, "&Sweep\tShift+Ctrl+S", MNU_GROUP_SWEEP, 'S'|S|C,mGrp }, { 1, NULL, 0, 0, NULL }, { 1, "Import / Assemble...\tShift+Ctrl+I", MNU_GROUP_IMPORT, 'I'|S|C,mGrp }, {11, "Import Recent", MNU_GROUP_RECENT, 0, mGrp }, @@ -235,8 +236,8 @@ void GraphicsWindow::LoopOverPoints( HandlePointForZoomToFit(e->PointGetNum(), pmax, pmin, wmin, div); } Group *g = SS.GetGroup(activeGroup); - for(i = 0; i < g->mesh.l.n; i++) { - STriangle *tr = &(g->mesh.l.elem[i]); + for(i = 0; i < g->runningMesh.l.n; i++) { + STriangle *tr = &(g->runningMesh.l.elem[i]); HandlePointForZoomToFit(tr->a, pmax, pmin, wmin, div); HandlePointForZoomToFit(tr->b, pmax, pmin, wmin, div); HandlePointForZoomToFit(tr->c, pmax, pmin, wmin, div); diff --git a/group.cpp b/group.cpp index b1d979a..e82a54f 100644 --- a/group.cpp +++ b/group.cpp @@ -100,6 +100,30 @@ void Group::MenuGroup(int id) { SS.GW.ClearSelection(); break; + case GraphicsWindow::MNU_GROUP_SWEEP: { + g.type = SWEEP; + // Get the group one before the active group; that's our + // trajectory + int i; + for(i = 1; i < SS.group.n - 1; i++) { + Group *gnext = &(SS.group.elem[i+1]); + if(gnext->h.v == SS.GW.activeGroup.v) { + g.opA = SS.group.elem[i].h; + break; + } + } + if(i >= SS.group.n - 1) { + Error("At least one sketch before the active sketch must " + "exist; that specifies the sweep trajectory."); + return; + } + // The active group is our section + g.opB = SS.GW.activeGroup; + g.color = RGB(100, 100, 100); + g.name.strcpy("sweep"); + break; + } + case GraphicsWindow::MNU_GROUP_ROT: { if(gs.points == 1 && gs.n == 1 && SS.GW.LockedInWorkplane()) { g.predef.origin = gs.point[0]; @@ -287,6 +311,10 @@ void Group::Generate(IdList *entity, break; } + case SWEEP: { + break; + } + case TRANSLATE: { // The translation vector AddParam(param, h.param(0), gp.x); diff --git a/groupmesh.cpp b/groupmesh.cpp index e09e384..bf07950 100644 --- a/groupmesh.cpp +++ b/groupmesh.cpp @@ -36,9 +36,209 @@ void Group::GeneratePolygon(void) { } } +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::GenerateMeshForSweep(void) { + STriMeta meta = { 0, color }; + SEdgeList edges; + ZERO(&edges); + int a, i; + + // The closed section that will be swept along the curve + Group *section = SS.GetGroup(opB); + (section->poly).MakeEdgesInto(&edges); + + // The trajectory along which the section will be swept + SContour traj; + ZERO(&traj); + 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 = 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); + } + + // Choose a new coordinate system, normal to the trajectory and + // with the minimum possible twist about the normal. + useNormal = useNormal.WithMagnitude(1); + Vector useV = (useNormal.Cross(oldU)).WithMagnitude(1); + Vector 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) { - SMesh outm; - ZERO(&outm); + thisMesh.Clear(); + STriMeta meta = { 0, color }; + if(type == EXTRUDE) { SEdgeList edges; ZERO(&edges); @@ -58,8 +258,6 @@ void Group::GenerateMesh(void) { SMesh srcm; ZERO(&srcm); (src->poly).TriangulateInto(&srcm); - STriMeta meta = { 0, color }; - // 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++) { @@ -68,9 +266,9 @@ void Group::GenerateMesh(void) { bt = (st->b).Plus(tbot), ct = (st->c).Plus(tbot); if(flipBottom) { - outm.AddTriangle(meta, ct, bt, at); + thisMesh.AddTriangle(meta, ct, bt, at); } else { - outm.AddTriangle(meta, at, bt, ct); + thisMesh.AddTriangle(meta, at, bt, ct); } } // And the top; that has the normal pointing the same dir as translate @@ -81,9 +279,9 @@ void Group::GenerateMesh(void) { bt = (st->b).Plus(ttop), ct = (st->c).Plus(ttop); if(flipBottom) { - outm.AddTriangle(meta, at, bt, ct); + thisMesh.AddTriangle(meta, at, bt, ct); } else { - outm.AddTriangle(meta, ct, bt, at); + thisMesh.AddTriangle(meta, ct, bt, at); } } srcm.Clear(); @@ -108,11 +306,11 @@ void Group::GenerateMesh(void) { meta.face = 0; } if(flipBottom) { - outm.AddTriangle(meta, bbot, abot, atop); - outm.AddTriangle(meta, bbot, atop, btop); + thisMesh.AddTriangle(meta, bbot, abot, atop); + thisMesh.AddTriangle(meta, bbot, atop, btop); } else { - outm.AddTriangle(meta, abot, bbot, atop); - outm.AddTriangle(meta, bbot, btop, atop); + thisMesh.AddTriangle(meta, abot, bbot, atop); + thisMesh.AddTriangle(meta, bbot, btop, atop); } } edges.Clear(); @@ -124,7 +322,6 @@ void Group::GenerateMesh(void) { Group *src = SS.GetGroup(opA); (src->poly).MakeEdgesInto(&edges); - STriMeta meta = { 0, color }; Vector orig = SS.GetEntity(predef.origin)->PointGetNum(); Vector axis = SS.GetEntity(predef.entityB)->VectorGetNum(); axis = axis.WithMagnitude(1); @@ -140,14 +337,11 @@ void Group::GenerateMesh(void) { } int n = SS.CircleSides(rmax); - for(a = 0; a <= n; a++) { + 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]); - double da = (edge->a).DistanceToLine(orig, axis); - double db = (edge->b).DistanceToLine(orig, axis); - Vector ai = (edge->a).RotatedAbout(orig, axis, thetai); Vector bi = (edge->b).RotatedAbout(orig, axis, thetai); Vector af = (edge->a).RotatedAbout(orig, axis, thetaf); @@ -158,24 +352,11 @@ void Group::GenerateMesh(void) { // This is a vector, not a point, so no origin for rotation out = out.RotatedAbout(axis, thetai); - // The line sweeps out a quad, so two triangles - STriangle quad1 = STriangle::From(meta, ai, bi, af), - quad2 = STriangle::From(meta, af, bi, bf); - - // 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(da >= LENGTH_EPS) outm.AddTriangle(&quad1); - if(db >= LENGTH_EPS) outm.AddTriangle(&quad2); + AddQuadWithNormal(meta, out, ai, bi, bf, af); } } + } else if(type == SWEEP) { + GenerateMeshForSweep(); } else if(type == IMPORTED) { // Triangles are just copied over, with the appropriate transformation // applied. @@ -199,31 +380,33 @@ void Group::GenerateMesh(void) { st.a = q.Rotate(st.a).Plus(offset); st.b = q.Rotate(st.b).Plus(offset); st.c = q.Rotate(st.c).Plus(offset); - outm.AddTriangle(&st); + thisMesh.AddTriangle(&st); } } - // So our group's mesh appears in outm. Combine this with the previous + // So our group's mesh appears in thisMesh. Combine this with the previous // group's mesh, using the requested operation. - mesh.Clear(); + runningMesh.Clear(); bool prevMeshError = meshError.yes; meshError.yes = false; meshError.interferesAt.Clear(); SMesh *a = PreviousGroupMesh(); if(meshCombine == COMBINE_AS_UNION) { - mesh.MakeFromUnion(a, &outm); + runningMesh.MakeFromUnion(a, &thisMesh); } else if(meshCombine == COMBINE_AS_DIFFERENCE) { - mesh.MakeFromDifference(a, &outm); + runningMesh.MakeFromDifference(a, &thisMesh); } else { - if(!mesh.MakeFromInterferenceCheck(a, &outm, &(meshError.interferesAt))) + if(!runningMesh.MakeFromInterferenceCheck(a, &thisMesh, + &(meshError.interferesAt))) + { meshError.yes = true; - // And the list of failed triangles appears in meshError.interferesAt + // 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; } - outm.Clear(); } SMesh *Group::PreviousGroupMesh(void) { @@ -233,7 +416,7 @@ SMesh *Group::PreviousGroupMesh(void) { if(g->h.v == h.v) break; } if(i == 0 || i >= SS.group.n) oops(); - return &(SS.group.elem[i-1].mesh); + return &(SS.group.elem[i-1].runningMesh); } void Group::Draw(void) { @@ -241,7 +424,7 @@ void Group::Draw(void) { // to show or hide just this with the "show solids" flag. int specColor; - if(type != EXTRUDE && type != IMPORTED && type != LATHE) { + if(type != EXTRUDE && type != IMPORTED && type != LATHE && type != SWEEP) { specColor = RGB(25, 25, 25); // force the color to something dim } else { specColor = -1; // use the model color @@ -263,7 +446,7 @@ void Group::Draw(void) { if(gs.faces > 1) ms2 = gs.face[1].v; glEnable(GL_LIGHTING); - if(SS.GW.showShaded) glxFillMesh(specColor, &mesh, mh, ms1, ms2); + if(SS.GW.showShaded) glxFillMesh(specColor, &runningMesh, mh, ms1, ms2); glDisable(GL_LIGHTING); if(meshError.yes) { @@ -283,7 +466,7 @@ void Group::Draw(void) { glDisable(GL_POLYGON_STIPPLE); } - if(SS.GW.showMesh) glxDebugMesh(&mesh); + if(SS.GW.showMesh) glxDebugMesh(&runningMesh); // And finally show the polygons too if(!SS.GW.showShaded) return; diff --git a/mesh.cpp b/mesh.cpp index 1217f0f..e83518d 100644 --- a/mesh.cpp +++ b/mesh.cpp @@ -4,14 +4,13 @@ void SMesh::Clear(void) { l.Clear(); } -void SMesh::AddTriangle(Vector n, Vector a, Vector b, Vector c) { +void SMesh::AddTriangle(STriMeta meta, Vector n, Vector a, Vector b, Vector c) { Vector ab = b.Minus(a), bc = c.Minus(b); Vector np = ab.Cross(bc); if(np.Magnitude() < 1e-10) { // ugh; gl sometimes tesselates to collinear triangles return; } - STriMeta meta; ZERO(&meta); if(np.Dot(n) > 0) { AddTriangle(meta, a, b, c); } else { diff --git a/polygon.cpp b/polygon.cpp index 3b61f9a..0655569 100644 --- a/polygon.cpp +++ b/polygon.cpp @@ -54,6 +54,46 @@ void SEdgeList::AddEdge(Vector a, Vector b) { l.Add(&e); } +bool SEdgeList::AssembleContour(Vector first, Vector last, + SContour *dest, SEdge *errorAt) +{ + int i; + + dest->AddPoint(first); + dest->AddPoint(last); + + do { + for(i = 0; i < l.n; i++) { + SEdge *se = &(l.elem[i]); + if(se->tag) continue; + + if(se->a.Equals(last)) { + dest->AddPoint(se->b); + last = se->b; + se->tag = 1; + break; + } + if(se->b.Equals(last)) { + dest->AddPoint(se->a); + last = se->a; + se->tag = 1; + break; + } + } + if(i >= l.n) { + // Couldn't assemble a closed contour; mark where. + if(errorAt) { + errorAt->a = first; + errorAt->b = last; + } + return false; + } + + } while(!last.Equals(first)); + + return true; +} + bool SEdgeList::AssemblePolygon(SPolygon *dest, SEdge *errorAt) { dest->Clear(); @@ -72,40 +112,22 @@ bool SEdgeList::AssemblePolygon(SPolygon *dest, SEdge *errorAt) { return true; } + // Create a new empty contour in our polygon, and finish assembling + // into that contour. dest->AddEmptyContour(); - dest->AddPoint(first); - dest->AddPoint(last); - do { - for(i = 0; i < l.n; i++) { - SEdge *se = &(l.elem[i]); - if(se->tag) continue; - - if(se->a.Equals(last)) { - dest->AddPoint(se->b); - last = se->b; - se->tag = 1; - break; - } - if(se->b.Equals(last)) { - dest->AddPoint(se->a); - last = se->a; - se->tag = 1; - break; - } - } - if(i >= l.n) { - // Couldn't assemble a closed contour; mark where. - if(errorAt) { - errorAt->a = first; - errorAt->b = last; - } - return false; - } - - } while(!last.Equals(first)); + if(!AssembleContour(first, last, &(dest->l.elem[dest->l.n-1]), errorAt)) + return false; } } +void SContour::AddPoint(Vector p) { + SPoint sp; + sp.tag = 0; + sp.p = p; + + l.Add(&sp); +} + void SContour::MakeEdgesInto(SEdgeList *el) { int i; for(i = 0; i < (l.n-1); i++) { @@ -216,17 +238,6 @@ void SPolygon::AddEmptyContour(void) { l.Add(&c); } -void SPolygon::AddPoint(Vector p) { - if(l.n < 1) oops(); - - SPoint sp; - sp.tag = 0; - sp.p = p; - - // Add to the last contour in the list - (l.elem[l.n-1]).l.Add(&sp); -} - void SPolygon::MakeEdgesInto(SEdgeList *el) { int i; for(i = 0; i < l.n; i++) { @@ -240,15 +251,19 @@ Vector SPolygon::ComputeNormal(void) { } bool SPolygon::ContainsPoint(Vector p) { - bool inside = false; + return (WindingNumberForPoint(p) % 2) == 1; +} + +int SPolygon::WindingNumberForPoint(Vector p) { + int winding = 0; int i; for(i = 0; i < l.n; i++) { SContour *sc = &(l.elem[i]); if(sc->ContainsPointProjdToNormal(normal, p)) { - inside = !inside; + winding++; } } - return inside; + return winding; } void SPolygon::FixContourDirections(void) { @@ -275,10 +290,20 @@ void SPolygon::FixContourDirections(void) { } } -bool SPolygon::AllPointsInPlane(Vector *notCoplanarAt) { +bool SPolygon::IsEmpty(void) { if(l.n == 0 || l.elem[0].l.n == 0) return true; + return false; +} - Vector p0 = l.elem[0].l.elem[0].p; +Vector SPolygon::AnyPoint(void) { + if(IsEmpty()) oops(); + return l.elem[0].l.elem[0].p; +} + +bool SPolygon::AllPointsInPlane(Vector *notCoplanarAt) { + if(IsEmpty()) return true; + + Vector p0 = AnyPoint(); double d = normal.Dot(p0); for(int i = 0; i < l.n; i++) { @@ -293,6 +318,7 @@ static int TriMode, TriVertexCount; static Vector Tri1, TriNMinus1, TriNMinus2; static Vector TriNormal; static SMesh *TriMesh; +static STriMeta TriMeta; static void GLX_CALLBACK TriBegin(int mode) { TriMode = mode; @@ -308,15 +334,18 @@ static void GLX_CALLBACK TriVertex(Vector *triN) } if(TriMode == GL_TRIANGLES) { if((TriVertexCount % 3) == 2) { - TriMesh->AddTriangle(TriNormal, TriNMinus2, TriNMinus1, *triN); + TriMesh->AddTriangle( + TriMeta, TriNormal, TriNMinus2, TriNMinus1, *triN); } } else if(TriMode == GL_TRIANGLE_FAN) { if(TriVertexCount >= 2) { - TriMesh->AddTriangle(TriNormal, Tri1, TriNMinus1, *triN); + TriMesh->AddTriangle( + TriMeta, TriNormal, Tri1, TriNMinus1, *triN); } } else if(TriMode == GL_TRIANGLE_STRIP) { if(TriVertexCount >= 2) { - TriMesh->AddTriangle(TriNormal, TriNMinus2, TriNMinus1, *triN); + TriMesh->AddTriangle( + TriMeta, TriNormal, TriNMinus2, TriNMinus1, *triN); } } else oops(); @@ -325,8 +354,14 @@ static void GLX_CALLBACK TriVertex(Vector *triN) TriVertexCount++; } void SPolygon::TriangulateInto(SMesh *m) { + STriMeta meta; + ZERO(&meta); + TriangulateInto(m, meta); +} +void SPolygon::TriangulateInto(SMesh *m, STriMeta meta) { TriMesh = m; TriNormal = normal; + TriMeta = meta; GLUtesselator *gt = gluNewTess(); gluTessCallback(gt, GLU_TESS_BEGIN, (glxCallbackFptr *)TriBegin); diff --git a/polygon.h b/polygon.h index b057e9f..4626d38 100644 --- a/polygon.h +++ b/polygon.h @@ -3,6 +3,7 @@ #define __POLYGON_H class SPolygon; +class SContour; class SMesh; class SBsp3; @@ -67,6 +68,8 @@ public: void Clear(void); void AddEdge(Vector a, Vector b); bool AssemblePolygon(SPolygon *dest, SEdge *errorAt); + bool AssembleContour(Vector first, Vector last, SContour *dest, + SEdge *errorAt); }; class SPoint { @@ -79,6 +82,7 @@ class SContour { public: SList l; + void AddPoint(Vector p); void MakeEdgesInto(SEdgeList *el); void Reverse(void); Vector ComputeNormal(void); @@ -87,6 +91,11 @@ public: bool AllPointsInPlane(Vector n, double d, Vector *notCoplanarAt); }; +typedef struct { + DWORD face; + int color; +} STriMeta; + class SPolygon { public: SList l; @@ -94,19 +103,18 @@ public: Vector ComputeNormal(void); void AddEmptyContour(void); - void AddPoint(Vector p); + int WindingNumberForPoint(Vector p); bool ContainsPoint(Vector p); void MakeEdgesInto(SEdgeList *el); void FixContourDirections(void); void TriangulateInto(SMesh *m); + void TriangulateInto(SMesh *m, STriMeta meta); void Clear(void); bool AllPointsInPlane(Vector *notCoplanarAt); + bool IsEmpty(void); + Vector AnyPoint(void); }; -typedef struct { - DWORD face; - int color; -} STriMeta; class STriangle { public: int tag; @@ -116,6 +124,7 @@ public: static STriangle From(STriMeta meta, Vector a, Vector b, Vector c); Vector Normal(void); void FlipNormal(void); + int WindingNumberForPoint(Vector p); bool ContainsPoint(Vector p); bool ContainsPointProjd(Vector n, Vector p); }; @@ -185,7 +194,7 @@ public: void Clear(void); void AddTriangle(STriangle *st); void AddTriangle(STriMeta meta, Vector a, Vector b, Vector c); - void AddTriangle(Vector n, Vector a, Vector b, Vector c); + void AddTriangle(STriMeta meta, Vector n, Vector a, Vector b, Vector c); void DoBounding(Vector v, Vector *vmax, Vector *vmin); void GetBounding(Vector *vmax, Vector *vmin); diff --git a/sketch.h b/sketch.h index 9c078ea..4cddf0a 100644 --- a/sketch.h +++ b/sketch.h @@ -82,12 +82,14 @@ public: static const int DRAWING_WORKPLANE = 5001; static const int EXTRUDE = 5100; static const int LATHE = 5101; + static const int SWEEP = 5102; static const int ROTATE = 5200; static const int TRANSLATE = 5201; static const int IMPORTED = 5300; int type; hGroup opA; + hGroup opB; bool visible; bool clean; hEntity activeWorkplane; @@ -130,7 +132,8 @@ public: Vector notCoplanarAt; } polyError; - SMesh mesh; + SMesh thisMesh; + SMesh runningMesh; struct { SMesh interferesAt; bool yes; @@ -179,8 +182,14 @@ public: void AddEq(IdList *l, Expr *expr, int index); void GenerateEquations(IdList *l); - SMesh *PreviousGroupMesh(void); + // Assembling piecewise linear sections into polygons void GeneratePolygon(void); + // And the mesh stuff + SMesh *PreviousGroupMesh(void); + void GetTrajectory(hGroup hg, SContour *traj, SPolygon *section); + void AddQuadWithNormal(STriMeta meta, Vector out, + Vector a, Vector b, Vector c, Vector d); + void GenerateMeshForSweep(void); void GenerateMesh(void); void Draw(void); diff --git a/solvespace.cpp b/solvespace.cpp index 73ec816..ce4006a 100644 --- a/solvespace.cpp +++ b/solvespace.cpp @@ -133,10 +133,15 @@ void SolveSpace::AfterNewFile(void) { GetGraphicsWindowSize(&w, &h); GW.width = w; GW.height = h; + + // The triangles haven't been generated yet, but zoom to fit the entities + // roughly in the window, since that sets the mesh tolerance. GW.ZoomToFit(); GenerateAll(0, INT_MAX); later.showTW = true; + // Then zoom to fit again, to fit the triangles + GW.ZoomToFit(); } void SolveSpace::MarkGroupDirtyByEntity(hEntity he) { @@ -481,6 +486,11 @@ void SolveSpace::ExportAsPngTo(char *filename) { png_init_io(png_ptr, f); + // glReadPixels wants to align things on 4-boundaries, and there's 3 + // bytes per pixel. As long as the row width is divisible by 4, all + // works out. + w &= ~3; h &= ~3; + png_set_IHDR(png_ptr, info_ptr, w, h, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,PNG_FILTER_TYPE_DEFAULT); diff --git a/textwin.cpp b/textwin.cpp index 0d69e32..ed9e09b 100644 --- a/textwin.cpp +++ b/textwin.cpp @@ -734,6 +734,7 @@ void TextWindow::ShowGroupInfo(void) { if(g->type == Group::EXTRUDE || g->type == Group::LATHE || + g->type == Group::SWEEP || g->type == Group::IMPORTED) { bool un = (g->meshCombine == Group::COMBINE_AS_UNION); @@ -759,7 +760,10 @@ void TextWindow::ShowGroupInfo(void) { Printf(false, "%Fx the parts interfere!"); } - if(g->type == Group::EXTRUDE || g->type == Group::LATHE) { + if(g->type == Group::EXTRUDE || + g->type == Group::LATHE || + g->type == Group::SWEEP) + { #define TWOX(v) v v Printf(true, "%FtM_COLOR%E " TWOX(TWOX(TWOX("%Bp%D%f%Ln %Bd%E "))), 0x80000000 | SS.modelColor[0], 0, &TextWindow::ScreenColor, @@ -913,7 +917,7 @@ void TextWindow::ShowConfiguration(void) { Printf(false, "%Ba %2 %Fl%Ll%f%D[change]%E; now %d triangles", SS.meshTol, &ScreenChangeMeshTolerance, 0, - SS.group.elem[SS.group.n-1].mesh.l.n); + SS.group.elem[SS.group.n-1].runningMesh.l.n); Printf(false, ""); Printf(false, "%Ft perspective factor (0 for isometric)%E"); diff --git a/ui.h b/ui.h index 416686c..76590f3 100644 --- a/ui.h +++ b/ui.h @@ -163,6 +163,7 @@ public: MNU_GROUP_WRKPL, MNU_GROUP_EXTRUDE, MNU_GROUP_LATHE, + MNU_GROUP_SWEEP, MNU_GROUP_ROT, MNU_GROUP_TRANS, MNU_GROUP_IMPORT, diff --git a/undoredo.cpp b/undoredo.cpp index b23011b..20d673a 100644 --- a/undoredo.cpp +++ b/undoredo.cpp @@ -48,7 +48,8 @@ void SolveSpace::PushFromCurrentOnto(UndoStack *uk) { ZERO(&(dest.solved)); ZERO(&(dest.poly)); ZERO(&(dest.polyError)); - ZERO(&(dest.mesh)); + ZERO(&(dest.thisMesh)); + ZERO(&(dest.runningMesh)); ZERO(&(dest.meshError)); ZERO(&(dest.remap)); @@ -87,7 +88,8 @@ void SolveSpace::PopOntoCurrentFrom(UndoStack *uk) { for(i = 0; i < group.n; i++) { Group *g = &(group.elem[i]); g->poly.Clear(); - g->mesh.Clear(); + g->thisMesh.Clear(); + g->runningMesh.Clear(); g->meshError.interferesAt.Clear(); g->remap.Clear(); g->impMesh.Clear(); diff --git a/util.cpp b/util.cpp index cc52c75..3afa780 100644 --- a/util.cpp +++ b/util.cpp @@ -310,6 +310,22 @@ Vector Vector::RotatedAbout(Vector axis, double theta) { return r; } +Vector Vector::DotInToCsys(Vector u, Vector v, Vector n) { + Vector r = { + this->Dot(u), + this->Dot(v), + this->Dot(n) + }; + return r; +} + +Vector Vector::ScaleOutOfCsys(Vector u, Vector v, Vector n) { + Vector r = u.ScaledBy(x).Plus( + v.ScaledBy(y).Plus( + n.ScaledBy(z))); + return r; +} + double Vector::DistanceToLine(Vector p0, Vector dp) { double m = dp.Magnitude(); return ((this->Minus(p0)).Cross(dp)).Magnitude() / m; diff --git a/wishlist.txt b/wishlist.txt index 190149e..2f47193 100644 --- a/wishlist.txt +++ b/wishlist.txt @@ -7,9 +7,9 @@ some kind of rounding / chamfer remove back button in browser? relative paths for import auto-generate circles and faces when lathing +copy the section geometry to other end when sweeping partitioned subsystems in the solver -sweep tool long term