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]
solver
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
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

View File

@ -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;

2
dsc.h
View File

@ -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);

View File

@ -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 "

View File

@ -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);

View File

@ -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,hEntity> *entity,
break;
}
case SWEEP: {
break;
}
case TRANSLATE: {
// The translation vector
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) {
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;

View File

@ -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 {

View File

@ -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);

View File

@ -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<SPoint> 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<SContour> 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);

View File

@ -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<Equation,hEquation> *l, Expr *expr, int index);
void GenerateEquations(IdList<Equation,hEquation> *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);

View File

@ -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);

View File

@ -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");

1
ui.h
View File

@ -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,

View File

@ -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();

View File

@ -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;

View File

@ -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