Add sweeps. The user specifies a trajectory and a section, in two

separate groups. The section is swept normal to the trajectory,
producing a mesh. I'm doing the triangles only now, not copying
over any entities.

Also fix a bug in the PNG export; rows are 4-aligned, so that was
breaking when the width of the image wasn't divisible by four. Also
fix a bug in lathes, where it generated overlapping triangles for
one segment.

And change the groups to record both "this mesh", the contribution
due to the extrude/lathe/whatever, and the "running mesh", that we
get after applying the requested Boolean op between "this mesh" and
the previous group's "running mesh". I'll use that to make step and
repeats step the mesh too.

[git-p4: depot-paths = "//depot/solvespace/": change = 1801]
This commit is contained in:
Jonathan Westhues 2008-06-21 02:18:20 -08:00
parent eb0b63f5dd
commit 5a22982e05
17 changed files with 420 additions and 118 deletions

View File

@ -1,5 +1,7 @@
DEFINES = /D_WIN32_WINNT=0x500 /DISOLATION_AWARE_ENABLED /D_WIN32_IE=0x500 /DWIN32_LEAN_AND_MEAN /DWIN32 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 HEADERS = ..\common\win32\freeze.h ui.h solvespace.h dsc.h sketch.h expr.h polygon.h

View File

@ -804,7 +804,7 @@ void GraphicsWindow::HitTestMakeSelection(Point2d mp) {
// Faces, from the triangle mesh; these are lowest priority // Faces, from the triangle mesh; these are lowest priority
if(s.constraint.v == 0 && s.entity.v == 0 && showShaded && showFaces) { 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); DWORD v = m->FirstIntersectionWith(mp);
if(v) { if(v) {
s.entity.v = v; s.entity.v = v;

2
dsc.h
View File

@ -55,6 +55,8 @@ public:
Vector Normal(int which); Vector Normal(int which);
Vector RotatedAbout(Vector orig, Vector axis, double theta); Vector RotatedAbout(Vector orig, Vector axis, double theta);
Vector RotatedAbout(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); double DistanceToLine(Vector p0, Vector dp);
Vector ClosestPointOnLine(Vector p0, Vector dp); Vector ClosestPointOnLine(Vector p0, Vector dp);
double Magnitude(void); double Magnitude(void);

View File

@ -65,6 +65,7 @@ const SolveSpace::SaveTable SolveSpace::SAVED[] = {
{ 'g', "Group.name", 'N', &(SS.sv.g.name) }, { 'g', "Group.name", 'N', &(SS.sv.g.name) },
{ 'g', "Group.activeWorkplane.v", 'x', &(SS.sv.g.activeWorkplane.v) }, { 'g', "Group.activeWorkplane.v", 'x', &(SS.sv.g.activeWorkplane.v) },
{ 'g', "Group.opA.v", 'x', &(SS.sv.g.opA.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.valA", 'f', &(SS.sv.g.valA) },
{ 'g', "Group.color", 'x', &(SS.sv.g.color) }, { 'g', "Group.color", 'x', &(SS.sv.g.color) },
{ 'g', "Group.subtype", 'd', &(SS.sv.g.subtype) }, { 'g', "Group.subtype", 'd', &(SS.sv.g.subtype) },
@ -219,7 +220,7 @@ bool SolveSpace::SaveToFile(char *filename) {
fprintf(fh, "AddConstraint\n\n"); 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++) { for(i = 0; i < m->l.n; i++) {
STriangle *tr = &(m->l.elem[i]); STriangle *tr = &(m->l.elem[i]);
fprintf(fh, "Triangle %08x %08x " fprintf(fh, "Triangle %08x %08x "

View File

@ -39,14 +39,15 @@ const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = {
{ 1, "Dimensions in &Millimeters", MNU_UNITS_MM, 0, mView }, { 1, "Dimensions in &Millimeters", MNU_UNITS_MM, 0, mView },
{ 0, "&New Group", 0, 0, NULL }, { 0, "&New Group", 0, 0, NULL },
{ 1, "&Drawing in 3d\tShift+Ctrl+D", MNU_GROUP_3D, 'D'|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, "Drawing in Workplane\tShift+Ctrl+W", MNU_GROUP_WRKPL, 'W'|S|C,mGrp },
{ 1, NULL, 0, NULL }, { 1, NULL, 0, NULL },
{ 1, "Step &Translating\tShift+Ctrl+R", MNU_GROUP_TRANS, 'T'|S|C,mGrp }, { 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, "Step &Rotating\tShift+Ctrl+T", MNU_GROUP_ROT, 'R'|S|C,mGrp },
{ 1, NULL, 0, 0, NULL }, { 1, NULL, 0, 0, NULL },
{ 1, "Extrude\tShift+Ctrl+X", MNU_GROUP_EXTRUDE, 'X'|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, "&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, NULL, 0, 0, NULL },
{ 1, "Import / Assemble...\tShift+Ctrl+I", MNU_GROUP_IMPORT, 'I'|S|C,mGrp }, { 1, "Import / Assemble...\tShift+Ctrl+I", MNU_GROUP_IMPORT, 'I'|S|C,mGrp },
{11, "Import Recent", MNU_GROUP_RECENT, 0, mGrp }, {11, "Import Recent", MNU_GROUP_RECENT, 0, mGrp },
@ -235,8 +236,8 @@ void GraphicsWindow::LoopOverPoints(
HandlePointForZoomToFit(e->PointGetNum(), pmax, pmin, wmin, div); HandlePointForZoomToFit(e->PointGetNum(), pmax, pmin, wmin, div);
} }
Group *g = SS.GetGroup(activeGroup); Group *g = SS.GetGroup(activeGroup);
for(i = 0; i < g->mesh.l.n; i++) { for(i = 0; i < g->runningMesh.l.n; i++) {
STriangle *tr = &(g->mesh.l.elem[i]); STriangle *tr = &(g->runningMesh.l.elem[i]);
HandlePointForZoomToFit(tr->a, pmax, pmin, wmin, div); HandlePointForZoomToFit(tr->a, pmax, pmin, wmin, div);
HandlePointForZoomToFit(tr->b, pmax, pmin, wmin, div); HandlePointForZoomToFit(tr->b, pmax, pmin, wmin, div);
HandlePointForZoomToFit(tr->c, pmax, pmin, wmin, div); HandlePointForZoomToFit(tr->c, pmax, pmin, wmin, div);

View File

@ -100,6 +100,30 @@ void Group::MenuGroup(int id) {
SS.GW.ClearSelection(); SS.GW.ClearSelection();
break; 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: { case GraphicsWindow::MNU_GROUP_ROT: {
if(gs.points == 1 && gs.n == 1 && SS.GW.LockedInWorkplane()) { if(gs.points == 1 && gs.n == 1 && SS.GW.LockedInWorkplane()) {
g.predef.origin = gs.point[0]; g.predef.origin = gs.point[0];
@ -287,6 +311,10 @@ void Group::Generate(IdList<Entity,hEntity> *entity,
break; break;
} }
case SWEEP: {
break;
}
case TRANSLATE: { case TRANSLATE: {
// The translation vector // The translation vector
AddParam(param, h.param(0), gp.x); AddParam(param, h.param(0), gp.x);

View File

@ -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) { void Group::GenerateMesh(void) {
SMesh outm; thisMesh.Clear();
ZERO(&outm); STriMeta meta = { 0, color };
if(type == EXTRUDE) { if(type == EXTRUDE) {
SEdgeList edges; SEdgeList edges;
ZERO(&edges); ZERO(&edges);
@ -58,8 +258,6 @@ void Group::GenerateMesh(void) {
SMesh srcm; ZERO(&srcm); SMesh srcm; ZERO(&srcm);
(src->poly).TriangulateInto(&srcm); (src->poly).TriangulateInto(&srcm);
STriMeta meta = { 0, color };
// Do the bottom; that has normal pointing opposite from translate // Do the bottom; that has normal pointing opposite from translate
meta.face = Remap(Entity::NO_ENTITY, REMAP_BOTTOM).v; meta.face = Remap(Entity::NO_ENTITY, REMAP_BOTTOM).v;
for(i = 0; i < srcm.l.n; i++) { for(i = 0; i < srcm.l.n; i++) {
@ -68,9 +266,9 @@ void Group::GenerateMesh(void) {
bt = (st->b).Plus(tbot), bt = (st->b).Plus(tbot),
ct = (st->c).Plus(tbot); ct = (st->c).Plus(tbot);
if(flipBottom) { if(flipBottom) {
outm.AddTriangle(meta, ct, bt, at); thisMesh.AddTriangle(meta, ct, bt, at);
} else { } 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 // 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), bt = (st->b).Plus(ttop),
ct = (st->c).Plus(ttop); ct = (st->c).Plus(ttop);
if(flipBottom) { if(flipBottom) {
outm.AddTriangle(meta, at, bt, ct); thisMesh.AddTriangle(meta, at, bt, ct);
} else { } else {
outm.AddTriangle(meta, ct, bt, at); thisMesh.AddTriangle(meta, ct, bt, at);
} }
} }
srcm.Clear(); srcm.Clear();
@ -108,11 +306,11 @@ void Group::GenerateMesh(void) {
meta.face = 0; meta.face = 0;
} }
if(flipBottom) { if(flipBottom) {
outm.AddTriangle(meta, bbot, abot, atop); thisMesh.AddTriangle(meta, bbot, abot, atop);
outm.AddTriangle(meta, bbot, atop, btop); thisMesh.AddTriangle(meta, bbot, atop, btop);
} else { } else {
outm.AddTriangle(meta, abot, bbot, atop); thisMesh.AddTriangle(meta, abot, bbot, atop);
outm.AddTriangle(meta, bbot, btop, atop); thisMesh.AddTriangle(meta, bbot, btop, atop);
} }
} }
edges.Clear(); edges.Clear();
@ -124,7 +322,6 @@ void Group::GenerateMesh(void) {
Group *src = SS.GetGroup(opA); Group *src = SS.GetGroup(opA);
(src->poly).MakeEdgesInto(&edges); (src->poly).MakeEdgesInto(&edges);
STriMeta meta = { 0, color };
Vector orig = SS.GetEntity(predef.origin)->PointGetNum(); Vector orig = SS.GetEntity(predef.origin)->PointGetNum();
Vector axis = SS.GetEntity(predef.entityB)->VectorGetNum(); Vector axis = SS.GetEntity(predef.entityB)->VectorGetNum();
axis = axis.WithMagnitude(1); axis = axis.WithMagnitude(1);
@ -140,14 +337,11 @@ void Group::GenerateMesh(void) {
} }
int n = SS.CircleSides(rmax); 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; double thetai = (2*PI*WRAP(a-1, n))/n, thetaf = (2*PI*a)/n;
for(i = 0; i < edges.l.n; i++) { for(i = 0; i < edges.l.n; i++) {
SEdge *edge = &(edges.l.elem[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 ai = (edge->a).RotatedAbout(orig, axis, thetai);
Vector bi = (edge->b).RotatedAbout(orig, axis, thetai); Vector bi = (edge->b).RotatedAbout(orig, axis, thetai);
Vector af = (edge->a).RotatedAbout(orig, axis, thetaf); 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 // This is a vector, not a point, so no origin for rotation
out = out.RotatedAbout(axis, thetai); out = out.RotatedAbout(axis, thetai);
// The line sweeps out a quad, so two triangles AddQuadWithNormal(meta, out, ai, bi, bf, af);
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);
} }
} }
} else if(type == SWEEP) {
GenerateMeshForSweep();
} else if(type == IMPORTED) { } else if(type == IMPORTED) {
// Triangles are just copied over, with the appropriate transformation // Triangles are just copied over, with the appropriate transformation
// applied. // applied.
@ -199,31 +380,33 @@ void Group::GenerateMesh(void) {
st.a = q.Rotate(st.a).Plus(offset); st.a = q.Rotate(st.a).Plus(offset);
st.b = q.Rotate(st.b).Plus(offset); st.b = q.Rotate(st.b).Plus(offset);
st.c = q.Rotate(st.c).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. // group's mesh, using the requested operation.
mesh.Clear(); runningMesh.Clear();
bool prevMeshError = meshError.yes; bool prevMeshError = meshError.yes;
meshError.yes = false; meshError.yes = false;
meshError.interferesAt.Clear(); meshError.interferesAt.Clear();
SMesh *a = PreviousGroupMesh(); SMesh *a = PreviousGroupMesh();
if(meshCombine == COMBINE_AS_UNION) { if(meshCombine == COMBINE_AS_UNION) {
mesh.MakeFromUnion(a, &outm); runningMesh.MakeFromUnion(a, &thisMesh);
} else if(meshCombine == COMBINE_AS_DIFFERENCE) { } else if(meshCombine == COMBINE_AS_DIFFERENCE) {
mesh.MakeFromDifference(a, &outm); runningMesh.MakeFromDifference(a, &thisMesh);
} else { } else {
if(!mesh.MakeFromInterferenceCheck(a, &outm, &(meshError.interferesAt))) if(!runningMesh.MakeFromInterferenceCheck(a, &thisMesh,
&(meshError.interferesAt)))
{
meshError.yes = true; 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) { if(prevMeshError != meshError.yes) {
// The error is reported in the text window for the group. // The error is reported in the text window for the group.
SS.later.showTW = true; SS.later.showTW = true;
} }
outm.Clear();
} }
SMesh *Group::PreviousGroupMesh(void) { SMesh *Group::PreviousGroupMesh(void) {
@ -233,7 +416,7 @@ SMesh *Group::PreviousGroupMesh(void) {
if(g->h.v == h.v) break; if(g->h.v == h.v) break;
} }
if(i == 0 || i >= SS.group.n) oops(); 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) { void Group::Draw(void) {
@ -241,7 +424,7 @@ void Group::Draw(void) {
// to show or hide just this with the "show solids" flag. // to show or hide just this with the "show solids" flag.
int specColor; 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 specColor = RGB(25, 25, 25); // force the color to something dim
} else { } else {
specColor = -1; // use the model color specColor = -1; // use the model color
@ -263,7 +446,7 @@ void Group::Draw(void) {
if(gs.faces > 1) ms2 = gs.face[1].v; if(gs.faces > 1) ms2 = gs.face[1].v;
glEnable(GL_LIGHTING); 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); glDisable(GL_LIGHTING);
if(meshError.yes) { if(meshError.yes) {
@ -283,7 +466,7 @@ void Group::Draw(void) {
glDisable(GL_POLYGON_STIPPLE); glDisable(GL_POLYGON_STIPPLE);
} }
if(SS.GW.showMesh) glxDebugMesh(&mesh); if(SS.GW.showMesh) glxDebugMesh(&runningMesh);
// And finally show the polygons too // And finally show the polygons too
if(!SS.GW.showShaded) return; if(!SS.GW.showShaded) return;

View File

@ -4,14 +4,13 @@ void SMesh::Clear(void) {
l.Clear(); 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 ab = b.Minus(a), bc = c.Minus(b);
Vector np = ab.Cross(bc); Vector np = ab.Cross(bc);
if(np.Magnitude() < 1e-10) { if(np.Magnitude() < 1e-10) {
// ugh; gl sometimes tesselates to collinear triangles // ugh; gl sometimes tesselates to collinear triangles
return; return;
} }
STriMeta meta; ZERO(&meta);
if(np.Dot(n) > 0) { if(np.Dot(n) > 0) {
AddTriangle(meta, a, b, c); AddTriangle(meta, a, b, c);
} else { } else {

View File

@ -54,6 +54,46 @@ void SEdgeList::AddEdge(Vector a, Vector b) {
l.Add(&e); 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) { bool SEdgeList::AssemblePolygon(SPolygon *dest, SEdge *errorAt) {
dest->Clear(); dest->Clear();
@ -72,40 +112,22 @@ bool SEdgeList::AssemblePolygon(SPolygon *dest, SEdge *errorAt) {
return true; return true;
} }
// Create a new empty contour in our polygon, and finish assembling
// into that contour.
dest->AddEmptyContour(); dest->AddEmptyContour();
dest->AddPoint(first); if(!AssembleContour(first, last, &(dest->l.elem[dest->l.n-1]), errorAt))
dest->AddPoint(last); return false;
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));
} }
} }
void SContour::AddPoint(Vector p) {
SPoint sp;
sp.tag = 0;
sp.p = p;
l.Add(&sp);
}
void SContour::MakeEdgesInto(SEdgeList *el) { void SContour::MakeEdgesInto(SEdgeList *el) {
int i; int i;
for(i = 0; i < (l.n-1); i++) { for(i = 0; i < (l.n-1); i++) {
@ -216,17 +238,6 @@ void SPolygon::AddEmptyContour(void) {
l.Add(&c); 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) { void SPolygon::MakeEdgesInto(SEdgeList *el) {
int i; int i;
for(i = 0; i < l.n; i++) { for(i = 0; i < l.n; i++) {
@ -240,15 +251,19 @@ Vector SPolygon::ComputeNormal(void) {
} }
bool SPolygon::ContainsPoint(Vector p) { bool SPolygon::ContainsPoint(Vector p) {
bool inside = false; return (WindingNumberForPoint(p) % 2) == 1;
}
int SPolygon::WindingNumberForPoint(Vector p) {
int winding = 0;
int i; int i;
for(i = 0; i < l.n; i++) { for(i = 0; i < l.n; i++) {
SContour *sc = &(l.elem[i]); SContour *sc = &(l.elem[i]);
if(sc->ContainsPointProjdToNormal(normal, p)) { if(sc->ContainsPointProjdToNormal(normal, p)) {
inside = !inside; winding++;
} }
} }
return inside; return winding;
} }
void SPolygon::FixContourDirections(void) { 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; 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); double d = normal.Dot(p0);
for(int i = 0; i < l.n; i++) { for(int i = 0; i < l.n; i++) {
@ -293,6 +318,7 @@ static int TriMode, TriVertexCount;
static Vector Tri1, TriNMinus1, TriNMinus2; static Vector Tri1, TriNMinus1, TriNMinus2;
static Vector TriNormal; static Vector TriNormal;
static SMesh *TriMesh; static SMesh *TriMesh;
static STriMeta TriMeta;
static void GLX_CALLBACK TriBegin(int mode) static void GLX_CALLBACK TriBegin(int mode)
{ {
TriMode = mode; TriMode = mode;
@ -308,15 +334,18 @@ static void GLX_CALLBACK TriVertex(Vector *triN)
} }
if(TriMode == GL_TRIANGLES) { if(TriMode == GL_TRIANGLES) {
if((TriVertexCount % 3) == 2) { if((TriVertexCount % 3) == 2) {
TriMesh->AddTriangle(TriNormal, TriNMinus2, TriNMinus1, *triN); TriMesh->AddTriangle(
TriMeta, TriNormal, TriNMinus2, TriNMinus1, *triN);
} }
} else if(TriMode == GL_TRIANGLE_FAN) { } else if(TriMode == GL_TRIANGLE_FAN) {
if(TriVertexCount >= 2) { if(TriVertexCount >= 2) {
TriMesh->AddTriangle(TriNormal, Tri1, TriNMinus1, *triN); TriMesh->AddTriangle(
TriMeta, TriNormal, Tri1, TriNMinus1, *triN);
} }
} else if(TriMode == GL_TRIANGLE_STRIP) { } else if(TriMode == GL_TRIANGLE_STRIP) {
if(TriVertexCount >= 2) { if(TriVertexCount >= 2) {
TriMesh->AddTriangle(TriNormal, TriNMinus2, TriNMinus1, *triN); TriMesh->AddTriangle(
TriMeta, TriNormal, TriNMinus2, TriNMinus1, *triN);
} }
} else oops(); } else oops();
@ -325,8 +354,14 @@ static void GLX_CALLBACK TriVertex(Vector *triN)
TriVertexCount++; TriVertexCount++;
} }
void SPolygon::TriangulateInto(SMesh *m) { void SPolygon::TriangulateInto(SMesh *m) {
STriMeta meta;
ZERO(&meta);
TriangulateInto(m, meta);
}
void SPolygon::TriangulateInto(SMesh *m, STriMeta meta) {
TriMesh = m; TriMesh = m;
TriNormal = normal; TriNormal = normal;
TriMeta = meta;
GLUtesselator *gt = gluNewTess(); GLUtesselator *gt = gluNewTess();
gluTessCallback(gt, GLU_TESS_BEGIN, (glxCallbackFptr *)TriBegin); gluTessCallback(gt, GLU_TESS_BEGIN, (glxCallbackFptr *)TriBegin);

View File

@ -3,6 +3,7 @@
#define __POLYGON_H #define __POLYGON_H
class SPolygon; class SPolygon;
class SContour;
class SMesh; class SMesh;
class SBsp3; class SBsp3;
@ -67,6 +68,8 @@ public:
void Clear(void); void Clear(void);
void AddEdge(Vector a, Vector b); void AddEdge(Vector a, Vector b);
bool AssemblePolygon(SPolygon *dest, SEdge *errorAt); bool AssemblePolygon(SPolygon *dest, SEdge *errorAt);
bool AssembleContour(Vector first, Vector last, SContour *dest,
SEdge *errorAt);
}; };
class SPoint { class SPoint {
@ -79,6 +82,7 @@ class SContour {
public: public:
SList<SPoint> l; SList<SPoint> l;
void AddPoint(Vector p);
void MakeEdgesInto(SEdgeList *el); void MakeEdgesInto(SEdgeList *el);
void Reverse(void); void Reverse(void);
Vector ComputeNormal(void); Vector ComputeNormal(void);
@ -87,6 +91,11 @@ public:
bool AllPointsInPlane(Vector n, double d, Vector *notCoplanarAt); bool AllPointsInPlane(Vector n, double d, Vector *notCoplanarAt);
}; };
typedef struct {
DWORD face;
int color;
} STriMeta;
class SPolygon { class SPolygon {
public: public:
SList<SContour> l; SList<SContour> l;
@ -94,19 +103,18 @@ public:
Vector ComputeNormal(void); Vector ComputeNormal(void);
void AddEmptyContour(void); void AddEmptyContour(void);
void AddPoint(Vector p); int WindingNumberForPoint(Vector p);
bool ContainsPoint(Vector p); bool ContainsPoint(Vector p);
void MakeEdgesInto(SEdgeList *el); void MakeEdgesInto(SEdgeList *el);
void FixContourDirections(void); void FixContourDirections(void);
void TriangulateInto(SMesh *m); void TriangulateInto(SMesh *m);
void TriangulateInto(SMesh *m, STriMeta meta);
void Clear(void); void Clear(void);
bool AllPointsInPlane(Vector *notCoplanarAt); bool AllPointsInPlane(Vector *notCoplanarAt);
bool IsEmpty(void);
Vector AnyPoint(void);
}; };
typedef struct {
DWORD face;
int color;
} STriMeta;
class STriangle { class STriangle {
public: public:
int tag; int tag;
@ -116,6 +124,7 @@ public:
static STriangle From(STriMeta meta, Vector a, Vector b, Vector c); static STriangle From(STriMeta meta, Vector a, Vector b, Vector c);
Vector Normal(void); Vector Normal(void);
void FlipNormal(void); void FlipNormal(void);
int WindingNumberForPoint(Vector p);
bool ContainsPoint(Vector p); bool ContainsPoint(Vector p);
bool ContainsPointProjd(Vector n, Vector p); bool ContainsPointProjd(Vector n, Vector p);
}; };
@ -185,7 +194,7 @@ public:
void Clear(void); void Clear(void);
void AddTriangle(STriangle *st); void AddTriangle(STriangle *st);
void AddTriangle(STriMeta meta, Vector a, Vector b, Vector c); 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 DoBounding(Vector v, Vector *vmax, Vector *vmin);
void GetBounding(Vector *vmax, Vector *vmin); void GetBounding(Vector *vmax, Vector *vmin);

View File

@ -82,12 +82,14 @@ public:
static const int DRAWING_WORKPLANE = 5001; static const int DRAWING_WORKPLANE = 5001;
static const int EXTRUDE = 5100; static const int EXTRUDE = 5100;
static const int LATHE = 5101; static const int LATHE = 5101;
static const int SWEEP = 5102;
static const int ROTATE = 5200; static const int ROTATE = 5200;
static const int TRANSLATE = 5201; static const int TRANSLATE = 5201;
static const int IMPORTED = 5300; static const int IMPORTED = 5300;
int type; int type;
hGroup opA; hGroup opA;
hGroup opB;
bool visible; bool visible;
bool clean; bool clean;
hEntity activeWorkplane; hEntity activeWorkplane;
@ -130,7 +132,8 @@ public:
Vector notCoplanarAt; Vector notCoplanarAt;
} polyError; } polyError;
SMesh mesh; SMesh thisMesh;
SMesh runningMesh;
struct { struct {
SMesh interferesAt; SMesh interferesAt;
bool yes; bool yes;
@ -179,8 +182,14 @@ public:
void AddEq(IdList<Equation,hEquation> *l, Expr *expr, int index); void AddEq(IdList<Equation,hEquation> *l, Expr *expr, int index);
void GenerateEquations(IdList<Equation,hEquation> *l); void GenerateEquations(IdList<Equation,hEquation> *l);
SMesh *PreviousGroupMesh(void); // Assembling piecewise linear sections into polygons
void GeneratePolygon(void); 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 GenerateMesh(void);
void Draw(void); void Draw(void);

View File

@ -133,10 +133,15 @@ void SolveSpace::AfterNewFile(void) {
GetGraphicsWindowSize(&w, &h); GetGraphicsWindowSize(&w, &h);
GW.width = w; GW.width = w;
GW.height = h; 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(); GW.ZoomToFit();
GenerateAll(0, INT_MAX); GenerateAll(0, INT_MAX);
later.showTW = true; later.showTW = true;
// Then zoom to fit again, to fit the triangles
GW.ZoomToFit();
} }
void SolveSpace::MarkGroupDirtyByEntity(hEntity he) { void SolveSpace::MarkGroupDirtyByEntity(hEntity he) {
@ -481,6 +486,11 @@ void SolveSpace::ExportAsPngTo(char *filename) {
png_init_io(png_ptr, f); 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, png_set_IHDR(png_ptr, info_ptr, w, h,
8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT,PNG_FILTER_TYPE_DEFAULT); PNG_COMPRESSION_TYPE_DEFAULT,PNG_FILTER_TYPE_DEFAULT);

View File

@ -734,6 +734,7 @@ void TextWindow::ShowGroupInfo(void) {
if(g->type == Group::EXTRUDE || if(g->type == Group::EXTRUDE ||
g->type == Group::LATHE || g->type == Group::LATHE ||
g->type == Group::SWEEP ||
g->type == Group::IMPORTED) g->type == Group::IMPORTED)
{ {
bool un = (g->meshCombine == Group::COMBINE_AS_UNION); bool un = (g->meshCombine == Group::COMBINE_AS_UNION);
@ -759,7 +760,10 @@ void TextWindow::ShowGroupInfo(void) {
Printf(false, "%Fx the parts interfere!"); 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 #define TWOX(v) v v
Printf(true, "%FtM_COLOR%E " TWOX(TWOX(TWOX("%Bp%D%f%Ln %Bd%E "))), Printf(true, "%FtM_COLOR%E " TWOX(TWOX(TWOX("%Bp%D%f%Ln %Bd%E "))),
0x80000000 | SS.modelColor[0], 0, &TextWindow::ScreenColor, 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", Printf(false, "%Ba %2 %Fl%Ll%f%D[change]%E; now %d triangles",
SS.meshTol, SS.meshTol,
&ScreenChangeMeshTolerance, 0, &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, "");
Printf(false, "%Ft perspective factor (0 for isometric)%E"); Printf(false, "%Ft perspective factor (0 for isometric)%E");

1
ui.h
View File

@ -163,6 +163,7 @@ public:
MNU_GROUP_WRKPL, MNU_GROUP_WRKPL,
MNU_GROUP_EXTRUDE, MNU_GROUP_EXTRUDE,
MNU_GROUP_LATHE, MNU_GROUP_LATHE,
MNU_GROUP_SWEEP,
MNU_GROUP_ROT, MNU_GROUP_ROT,
MNU_GROUP_TRANS, MNU_GROUP_TRANS,
MNU_GROUP_IMPORT, MNU_GROUP_IMPORT,

View File

@ -48,7 +48,8 @@ void SolveSpace::PushFromCurrentOnto(UndoStack *uk) {
ZERO(&(dest.solved)); ZERO(&(dest.solved));
ZERO(&(dest.poly)); ZERO(&(dest.poly));
ZERO(&(dest.polyError)); ZERO(&(dest.polyError));
ZERO(&(dest.mesh)); ZERO(&(dest.thisMesh));
ZERO(&(dest.runningMesh));
ZERO(&(dest.meshError)); ZERO(&(dest.meshError));
ZERO(&(dest.remap)); ZERO(&(dest.remap));
@ -87,7 +88,8 @@ void SolveSpace::PopOntoCurrentFrom(UndoStack *uk) {
for(i = 0; i < group.n; i++) { for(i = 0; i < group.n; i++) {
Group *g = &(group.elem[i]); Group *g = &(group.elem[i]);
g->poly.Clear(); g->poly.Clear();
g->mesh.Clear(); g->thisMesh.Clear();
g->runningMesh.Clear();
g->meshError.interferesAt.Clear(); g->meshError.interferesAt.Clear();
g->remap.Clear(); g->remap.Clear();
g->impMesh.Clear(); g->impMesh.Clear();

View File

@ -310,6 +310,22 @@ Vector Vector::RotatedAbout(Vector axis, double theta) {
return r; 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 Vector::DistanceToLine(Vector p0, Vector dp) {
double m = dp.Magnitude(); double m = dp.Magnitude();
return ((this->Minus(p0)).Cross(dp)).Magnitude() / m; return ((this->Minus(p0)).Cross(dp)).Magnitude() / m;

View File

@ -7,9 +7,9 @@ some kind of rounding / chamfer
remove back button in browser? remove back button in browser?
relative paths for import relative paths for import
auto-generate circles and faces when lathing auto-generate circles and faces when lathing
copy the section geometry to other end when sweeping
partitioned subsystems in the solver partitioned subsystems in the solver
sweep tool
long term long term