Split ratpoly.cpp; now that contains only the mathematical stuff,
and curve.cpp and surface.cpp contain the rest. Also get rid of the meshError stuff; will just use the nakedEdges mechanism for that. And I won't run the interference test continuously, have added a menu item for that. [git-p4: depot-paths = "//depot/solvespace/": change = 1934]solver
parent
7f3dd91bd9
commit
71adc0bf54
4
Makefile
4
Makefile
|
@ -40,6 +40,8 @@ SSOBJS = $(OBJDIR)\solvespace.obj \
|
|||
$(OBJDIR)\export.obj \
|
||||
|
||||
SRFOBJS = $(OBJDIR)\ratpoly.obj \
|
||||
$(OBJDIR)\curve.obj \
|
||||
$(OBJDIR)\surface.obj \
|
||||
$(OBJDIR)\triangulate.obj \
|
||||
$(OBJDIR)\boolean.obj \
|
||||
$(OBJDIR)\surfinter.obj \
|
||||
|
@ -53,7 +55,7 @@ LIBS = user32.lib gdi32.lib comctl32.lib advapi32.lib shell32.lib opengl32.lib g
|
|||
|
||||
all: $(OBJDIR)/solvespace.exe
|
||||
@cp $(OBJDIR)/solvespace.exe .
|
||||
solvespace tttt.slvs
|
||||
solvespace t7.slvs
|
||||
|
||||
clean:
|
||||
rm -f obj/*
|
||||
|
|
|
@ -116,9 +116,9 @@ void SolveSpace::ExportViewTo(char *filename) {
|
|||
}
|
||||
|
||||
if(SS.GW.showEdges) {
|
||||
SEdgeList *emph = &((SS.GetGroup(SS.GW.activeGroup))->emphEdges);
|
||||
SEdgeList *selr = &((SS.GetGroup(SS.GW.activeGroup))->runningEdges);
|
||||
SEdge *se;
|
||||
for(se = emph->l.First(); se; se = emph->l.NextAfter(se)) {
|
||||
for(se = selr->l.First(); se; se = selr->l.NextAfter(se)) {
|
||||
edges.AddEdge(se->a, se->b);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,6 +105,7 @@ const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = {
|
|||
|
||||
{ 0, "&Analyze", 0, NULL },
|
||||
{ 1, "Measure &Volume\tCtrl+Shift+V", MNU_VOLUME, 'V'|S|C,mAna },
|
||||
{ 1, "Show &Interfering Parts\tCtrl+Shift+I", MNU_INTERFERENCE, 'I'|S|C,mAna },
|
||||
{ 1, "Show &Naked Edges\tCtrl+Shift+N", MNU_NAKED_EDGES, 'N'|S|C,mAna },
|
||||
{ 1, NULL, 0, NULL },
|
||||
{ 1, "Show Degrees of &Freedom\tCtrl+Shift+F", MNU_SHOW_DOF, 'F'|S|C,mAna },
|
||||
|
|
|
@ -227,33 +227,20 @@ void Group::GenerateShellAndMesh(void) {
|
|||
|
||||
// So our group's mesh appears in thisMesh. Combine this with the previous
|
||||
// group's mesh, using the requested operation.
|
||||
bool prevMeshError = meshError.yes;
|
||||
|
||||
meshError.yes = false;
|
||||
meshError.interferesAt.Clear();
|
||||
|
||||
SShell *a = PreviousGroupShell();
|
||||
if(meshCombine == COMBINE_AS_UNION) {
|
||||
runningShell.MakeFromUnionOf(a, &thisShell);
|
||||
} else if(meshCombine == COMBINE_AS_DIFFERENCE) {
|
||||
runningShell.MakeFromDifferenceOf(a, &thisShell);
|
||||
} else {
|
||||
if(0) //&(meshError.interferesAt)
|
||||
{
|
||||
meshError.yes = true;
|
||||
// And the list of failed triangles goes in meshError.interferesAt
|
||||
}
|
||||
}
|
||||
if(prevMeshError != meshError.yes) {
|
||||
// The error is reported in the text window for the group.
|
||||
SS.later.showTW = true;
|
||||
// TODO, assembly
|
||||
}
|
||||
|
||||
done:
|
||||
runningMesh.Clear();
|
||||
runningShell.TriangulateInto(&runningMesh);
|
||||
emphEdges.Clear();
|
||||
runningShell.MakeEdgesInto(&emphEdges);
|
||||
runningEdges.Clear();
|
||||
runningShell.MakeEdgesInto(&runningEdges);
|
||||
}
|
||||
|
||||
SShell *Group::PreviousGroupShell(void) {
|
||||
|
@ -303,24 +290,7 @@ void Group::Draw(void) {
|
|||
glxColor3d(REDf (SS.edgeColor),
|
||||
GREENf(SS.edgeColor),
|
||||
BLUEf (SS.edgeColor));
|
||||
glxDrawEdges(&emphEdges);
|
||||
}
|
||||
|
||||
if(meshError.yes) {
|
||||
// Draw the error triangles in bright red stripes, with no Z buffering
|
||||
GLubyte mask[32*32/8];
|
||||
memset(mask, 0xf0, sizeof(mask));
|
||||
glPolygonStipple(mask);
|
||||
|
||||
int specColor = 0;
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glColor3d(0, 0, 0);
|
||||
glxFillMesh(0, &meshError.interferesAt, 0, 0, 0);
|
||||
glEnable(GL_POLYGON_STIPPLE);
|
||||
glColor3d(1, 0, 0);
|
||||
glxFillMesh(0, &meshError.interferesAt, 0, 0, 0);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glDisable(GL_POLYGON_STIPPLE);
|
||||
glxDrawEdges(&runningEdges);
|
||||
}
|
||||
|
||||
if(SS.GW.showMesh) glxDebugMesh(&runningMesh);
|
||||
|
|
7
sketch.h
7
sketch.h
|
@ -150,12 +150,9 @@ public:
|
|||
|
||||
SShell thisShell;
|
||||
SShell runningShell;
|
||||
|
||||
SMesh runningMesh;
|
||||
struct {
|
||||
SMesh interferesAt;
|
||||
bool yes;
|
||||
} meshError;
|
||||
SEdgeList emphEdges;
|
||||
SEdgeList runningEdges;
|
||||
|
||||
static const int COMBINE_AS_UNION = 0;
|
||||
static const int COMBINE_AS_DIFFERENCE = 1;
|
||||
|
|
|
@ -500,6 +500,9 @@ void SolveSpace::MenuAnalyze(int id) {
|
|||
break;
|
||||
}
|
||||
|
||||
case GraphicsWindow::MNU_INTERFERENCE:
|
||||
break;
|
||||
|
||||
case GraphicsWindow::MNU_SHOW_DOF:
|
||||
// This works like a normal solve, except that it calculates
|
||||
// which variables are free/bound at the same time.
|
||||
|
|
|
@ -0,0 +1,290 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Anything involving curves and sets of curves (except for the real math,
|
||||
// which is in ratpoly.cpp).
|
||||
//-----------------------------------------------------------------------------
|
||||
#include "../solvespace.h"
|
||||
|
||||
SBezier SBezier::From(Vector p0, Vector p1) {
|
||||
SBezier ret;
|
||||
ZERO(&ret);
|
||||
ret.deg = 1;
|
||||
ret.weight[0] = ret.weight[1] = 1;
|
||||
ret.ctrl[0] = p0;
|
||||
ret.ctrl[1] = p1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
SBezier SBezier::From(Vector p0, Vector p1, Vector p2) {
|
||||
SBezier ret;
|
||||
ZERO(&ret);
|
||||
ret.deg = 2;
|
||||
ret.weight[0] = ret.weight[1] = ret.weight[2] = 1;
|
||||
ret.ctrl[0] = p0;
|
||||
ret.ctrl[1] = p1;
|
||||
ret.ctrl[2] = p2;
|
||||
return ret;
|
||||
}
|
||||
|
||||
SBezier SBezier::From(Vector p0, Vector p1, Vector p2, Vector p3) {
|
||||
SBezier ret;
|
||||
ZERO(&ret);
|
||||
ret.deg = 3;
|
||||
ret.weight[0] = ret.weight[1] = ret.weight[2] = ret.weight[3] = 1;
|
||||
ret.ctrl[0] = p0;
|
||||
ret.ctrl[1] = p1;
|
||||
ret.ctrl[2] = p2;
|
||||
ret.ctrl[3] = p3;
|
||||
return ret;
|
||||
}
|
||||
|
||||
Vector SBezier::Start(void) {
|
||||
return ctrl[0];
|
||||
}
|
||||
|
||||
Vector SBezier::Finish(void) {
|
||||
return ctrl[deg];
|
||||
}
|
||||
|
||||
void SBezier::Reverse(void) {
|
||||
int i;
|
||||
for(i = 0; i < (deg+1)/2; i++) {
|
||||
SWAP(Vector, ctrl[i], ctrl[deg-i]);
|
||||
SWAP(double, weight[i], weight[deg-i]);
|
||||
}
|
||||
}
|
||||
|
||||
void SBezier::GetBoundingProjd(Vector u, Vector orig,
|
||||
double *umin, double *umax)
|
||||
{
|
||||
int i;
|
||||
for(i = 0; i <= deg; i++) {
|
||||
double ut = ((ctrl[i]).Minus(orig)).Dot(u);
|
||||
if(ut < *umin) *umin = ut;
|
||||
if(ut > *umax) *umax = ut;
|
||||
}
|
||||
}
|
||||
|
||||
SBezier SBezier::TransformedBy(Vector t, Quaternion q) {
|
||||
SBezier ret = *this;
|
||||
int i;
|
||||
for(i = 0; i <= deg; i++) {
|
||||
ret.ctrl[i] = (q.Rotate(ret.ctrl[i])).Plus(t);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool SBezier::Equals(SBezier *b) {
|
||||
// We just test of identical degree and control points, even though two
|
||||
// curves could still be coincident (even sharing endpoints).
|
||||
if(deg != b->deg) return false;
|
||||
int i;
|
||||
for(i = 0; i <= deg; i++) {
|
||||
if(!(ctrl[i]).Equals(b->ctrl[i])) return false;
|
||||
if(fabs(weight[i] - b->weight[i]) > LENGTH_EPS) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SBezierList::Clear(void) {
|
||||
l.Clear();
|
||||
}
|
||||
|
||||
|
||||
SBezierLoop SBezierLoop::FromCurves(SBezierList *sbl,
|
||||
bool *allClosed, SEdge *errorAt)
|
||||
{
|
||||
SBezierLoop loop;
|
||||
ZERO(&loop);
|
||||
|
||||
if(sbl->l.n < 1) return loop;
|
||||
sbl->l.ClearTags();
|
||||
|
||||
SBezier *first = &(sbl->l.elem[0]);
|
||||
first->tag = 1;
|
||||
loop.l.Add(first);
|
||||
Vector start = first->Start();
|
||||
Vector hanging = first->Finish();
|
||||
|
||||
sbl->l.RemoveTagged();
|
||||
|
||||
while(sbl->l.n > 0 && !hanging.Equals(start)) {
|
||||
int i;
|
||||
bool foundNext = false;
|
||||
for(i = 0; i < sbl->l.n; i++) {
|
||||
SBezier *test = &(sbl->l.elem[i]);
|
||||
|
||||
if((test->Finish()).Equals(hanging)) {
|
||||
test->Reverse();
|
||||
// and let the next test catch it
|
||||
}
|
||||
if((test->Start()).Equals(hanging)) {
|
||||
test->tag = 1;
|
||||
loop.l.Add(test);
|
||||
hanging = test->Finish();
|
||||
sbl->l.RemoveTagged();
|
||||
foundNext = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!foundNext) {
|
||||
// The loop completed without finding the hanging edge, so
|
||||
// it's an open loop
|
||||
errorAt->a = hanging;
|
||||
errorAt->b = start;
|
||||
*allClosed = false;
|
||||
return loop;
|
||||
}
|
||||
}
|
||||
if(hanging.Equals(start)) {
|
||||
*allClosed = true;
|
||||
} else {
|
||||
// We ran out of edges without forming a closed loop.
|
||||
errorAt->a = hanging;
|
||||
errorAt->b = start;
|
||||
*allClosed = false;
|
||||
}
|
||||
|
||||
return loop;
|
||||
}
|
||||
|
||||
void SBezierLoop::Reverse(void) {
|
||||
l.Reverse();
|
||||
SBezier *sb;
|
||||
for(sb = l.First(); sb; sb = l.NextAfter(sb)) {
|
||||
// If we didn't reverse each curve, then the next curve in list would
|
||||
// share your start, not your finish.
|
||||
sb->Reverse();
|
||||
}
|
||||
}
|
||||
|
||||
void SBezierLoop::GetBoundingProjd(Vector u, Vector orig,
|
||||
double *umin, double *umax)
|
||||
{
|
||||
SBezier *sb;
|
||||
for(sb = l.First(); sb; sb = l.NextAfter(sb)) {
|
||||
sb->GetBoundingProjd(u, orig, umin, umax);
|
||||
}
|
||||
}
|
||||
|
||||
void SBezierLoop::MakePwlInto(SContour *sc) {
|
||||
List<Vector> lv;
|
||||
ZERO(&lv);
|
||||
|
||||
int i, j;
|
||||
for(i = 0; i < l.n; i++) {
|
||||
SBezier *sb = &(l.elem[i]);
|
||||
sb->MakePwlInto(&lv);
|
||||
|
||||
// Each curve's piecewise linearization includes its endpoints,
|
||||
// which we don't want to duplicate (creating zero-len edges).
|
||||
for(j = (i == 0 ? 0 : 1); j < lv.n; j++) {
|
||||
sc->AddPoint(lv.elem[j]);
|
||||
}
|
||||
lv.Clear();
|
||||
}
|
||||
// Ensure that it's exactly closed, not just within a numerical tolerance.
|
||||
sc->l.elem[sc->l.n - 1] = sc->l.elem[0];
|
||||
}
|
||||
|
||||
|
||||
SBezierLoopSet SBezierLoopSet::From(SBezierList *sbl, SPolygon *poly,
|
||||
bool *allClosed, SEdge *errorAt)
|
||||
{
|
||||
int i;
|
||||
SBezierLoopSet ret;
|
||||
ZERO(&ret);
|
||||
|
||||
while(sbl->l.n > 0) {
|
||||
bool thisClosed;
|
||||
SBezierLoop loop;
|
||||
loop = SBezierLoop::FromCurves(sbl, &thisClosed, errorAt);
|
||||
if(!thisClosed) {
|
||||
ret.Clear();
|
||||
*allClosed = false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret.l.Add(&loop);
|
||||
poly->AddEmptyContour();
|
||||
loop.MakePwlInto(&(poly->l.elem[poly->l.n-1]));
|
||||
}
|
||||
|
||||
poly->normal = poly->ComputeNormal();
|
||||
ret.normal = poly->normal;
|
||||
if(poly->l.n > 0) {
|
||||
ret.point = poly->AnyPoint();
|
||||
} else {
|
||||
ret.point = Vector::From(0, 0, 0);
|
||||
}
|
||||
poly->FixContourDirections();
|
||||
|
||||
for(i = 0; i < poly->l.n; i++) {
|
||||
if(poly->l.elem[i].tag) {
|
||||
// We had to reverse this contour in order to fix the poly
|
||||
// contour directions; so need to do the same with the curves.
|
||||
ret.l.elem[i].Reverse();
|
||||
}
|
||||
}
|
||||
|
||||
*allClosed = true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SBezierLoopSet::GetBoundingProjd(Vector u, Vector orig,
|
||||
double *umin, double *umax)
|
||||
{
|
||||
SBezierLoop *sbl;
|
||||
for(sbl = l.First(); sbl; sbl = l.NextAfter(sbl)) {
|
||||
sbl->GetBoundingProjd(u, orig, umin, umax);
|
||||
}
|
||||
}
|
||||
|
||||
void SBezierLoopSet::Clear(void) {
|
||||
int i;
|
||||
for(i = 0; i < l.n; i++) {
|
||||
(l.elem[i]).Clear();
|
||||
}
|
||||
l.Clear();
|
||||
}
|
||||
|
||||
SCurve SCurve::FromTransformationOf(SCurve *a, Vector t, Quaternion q) {
|
||||
SCurve ret;
|
||||
ZERO(&ret);
|
||||
|
||||
ret.h = a->h;
|
||||
ret.isExact = a->isExact;
|
||||
ret.exact = (a->exact).TransformedBy(t, q);
|
||||
ret.surfA = a->surfA;
|
||||
ret.surfB = a->surfB;
|
||||
|
||||
Vector *p;
|
||||
for(p = a->pts.First(); p; p = a->pts.NextAfter(p)) {
|
||||
Vector pp = (q.Rotate(*p)).Plus(t);
|
||||
ret.pts.Add(&pp);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SCurve::Clear(void) {
|
||||
pts.Clear();
|
||||
}
|
||||
|
||||
STrimBy STrimBy::EntireCurve(SShell *shell, hSCurve hsc, bool backwards) {
|
||||
STrimBy stb;
|
||||
ZERO(&stb);
|
||||
stb.curve = hsc;
|
||||
SCurve *sc = shell->curve.FindById(hsc);
|
||||
|
||||
if(backwards) {
|
||||
stb.finish = sc->pts.elem[0];
|
||||
stb.start = sc->pts.elem[sc->pts.n - 1];
|
||||
stb.backwards = true;
|
||||
} else {
|
||||
stb.start = sc->pts.elem[0];
|
||||
stb.finish = sc->pts.elem[sc->pts.n - 1];
|
||||
stb.backwards = false;
|
||||
}
|
||||
|
||||
return stb;
|
||||
}
|
||||
|
744
srf/ratpoly.cpp
744
srf/ratpoly.cpp
|
@ -1,3 +1,8 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Math on rational polynomial surfaces and curves, typically in Bezier
|
||||
// form. Evaluate, root-find (by Newton's methods), evaluate derivatives,
|
||||
// and so on.
|
||||
//-----------------------------------------------------------------------------
|
||||
#include "../solvespace.h"
|
||||
|
||||
// Converge it to better than LENGTH_EPS; we want two points, each
|
||||
|
@ -88,47 +93,6 @@ double BernsteinDerivative(int k, int deg, double t)
|
|||
oops();
|
||||
}
|
||||
|
||||
SBezier SBezier::From(Vector p0, Vector p1) {
|
||||
SBezier ret;
|
||||
ZERO(&ret);
|
||||
ret.deg = 1;
|
||||
ret.weight[0] = ret.weight[1] = 1;
|
||||
ret.ctrl[0] = p0;
|
||||
ret.ctrl[1] = p1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
SBezier SBezier::From(Vector p0, Vector p1, Vector p2) {
|
||||
SBezier ret;
|
||||
ZERO(&ret);
|
||||
ret.deg = 2;
|
||||
ret.weight[0] = ret.weight[1] = ret.weight[2] = 1;
|
||||
ret.ctrl[0] = p0;
|
||||
ret.ctrl[1] = p1;
|
||||
ret.ctrl[2] = p2;
|
||||
return ret;
|
||||
}
|
||||
|
||||
SBezier SBezier::From(Vector p0, Vector p1, Vector p2, Vector p3) {
|
||||
SBezier ret;
|
||||
ZERO(&ret);
|
||||
ret.deg = 3;
|
||||
ret.weight[0] = ret.weight[1] = ret.weight[2] = ret.weight[3] = 1;
|
||||
ret.ctrl[0] = p0;
|
||||
ret.ctrl[1] = p1;
|
||||
ret.ctrl[2] = p2;
|
||||
ret.ctrl[3] = p3;
|
||||
return ret;
|
||||
}
|
||||
|
||||
Vector SBezier::Start(void) {
|
||||
return ctrl[0];
|
||||
}
|
||||
|
||||
Vector SBezier::Finish(void) {
|
||||
return ctrl[deg];
|
||||
}
|
||||
|
||||
Vector SBezier::PointAt(double t) {
|
||||
Vector pt = Vector::From(0, 0, 0);
|
||||
double d = 0;
|
||||
|
@ -172,394 +136,6 @@ void SBezier::MakePwlWorker(List<Vector> *l, double ta, double tb) {
|
|||
}
|
||||
}
|
||||
|
||||
void SBezier::Reverse(void) {
|
||||
int i;
|
||||
for(i = 0; i < (deg+1)/2; i++) {
|
||||
SWAP(Vector, ctrl[i], ctrl[deg-i]);
|
||||
SWAP(double, weight[i], weight[deg-i]);
|
||||
}
|
||||
}
|
||||
|
||||
void SBezier::GetBoundingProjd(Vector u, Vector orig,
|
||||
double *umin, double *umax)
|
||||
{
|
||||
int i;
|
||||
for(i = 0; i <= deg; i++) {
|
||||
double ut = ((ctrl[i]).Minus(orig)).Dot(u);
|
||||
if(ut < *umin) *umin = ut;
|
||||
if(ut > *umax) *umax = ut;
|
||||
}
|
||||
}
|
||||
|
||||
SBezier SBezier::TransformedBy(Vector t, Quaternion q) {
|
||||
SBezier ret = *this;
|
||||
int i;
|
||||
for(i = 0; i <= deg; i++) {
|
||||
ret.ctrl[i] = (q.Rotate(ret.ctrl[i])).Plus(t);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool SBezier::Equals(SBezier *b) {
|
||||
// We just test of identical degree and control points, even though two
|
||||
// curves could still be coincident (even sharing endpoints).
|
||||
if(deg != b->deg) return false;
|
||||
int i;
|
||||
for(i = 0; i <= deg; i++) {
|
||||
if(!(ctrl[i]).Equals(b->ctrl[i])) return false;
|
||||
if(fabs(weight[i] - b->weight[i]) > LENGTH_EPS) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SBezierList::Clear(void) {
|
||||
l.Clear();
|
||||
}
|
||||
|
||||
|
||||
SBezierLoop SBezierLoop::FromCurves(SBezierList *sbl,
|
||||
bool *allClosed, SEdge *errorAt)
|
||||
{
|
||||
SBezierLoop loop;
|
||||
ZERO(&loop);
|
||||
|
||||
if(sbl->l.n < 1) return loop;
|
||||
sbl->l.ClearTags();
|
||||
|
||||
SBezier *first = &(sbl->l.elem[0]);
|
||||
first->tag = 1;
|
||||
loop.l.Add(first);
|
||||
Vector start = first->Start();
|
||||
Vector hanging = first->Finish();
|
||||
|
||||
sbl->l.RemoveTagged();
|
||||
|
||||
while(sbl->l.n > 0 && !hanging.Equals(start)) {
|
||||
int i;
|
||||
bool foundNext = false;
|
||||
for(i = 0; i < sbl->l.n; i++) {
|
||||
SBezier *test = &(sbl->l.elem[i]);
|
||||
|
||||
if((test->Finish()).Equals(hanging)) {
|
||||
test->Reverse();
|
||||
// and let the next test catch it
|
||||
}
|
||||
if((test->Start()).Equals(hanging)) {
|
||||
test->tag = 1;
|
||||
loop.l.Add(test);
|
||||
hanging = test->Finish();
|
||||
sbl->l.RemoveTagged();
|
||||
foundNext = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!foundNext) {
|
||||
// The loop completed without finding the hanging edge, so
|
||||
// it's an open loop
|
||||
errorAt->a = hanging;
|
||||
errorAt->b = start;
|
||||
*allClosed = false;
|
||||
return loop;
|
||||
}
|
||||
}
|
||||
if(hanging.Equals(start)) {
|
||||
*allClosed = true;
|
||||
} else {
|
||||
// We ran out of edges without forming a closed loop.
|
||||
errorAt->a = hanging;
|
||||
errorAt->b = start;
|
||||
*allClosed = false;
|
||||
}
|
||||
|
||||
return loop;
|
||||
}
|
||||
|
||||
void SBezierLoop::Reverse(void) {
|
||||
l.Reverse();
|
||||
SBezier *sb;
|
||||
for(sb = l.First(); sb; sb = l.NextAfter(sb)) {
|
||||
// If we didn't reverse each curve, then the next curve in list would
|
||||
// share your start, not your finish.
|
||||
sb->Reverse();
|
||||
}
|
||||
}
|
||||
|
||||
void SBezierLoop::GetBoundingProjd(Vector u, Vector orig,
|
||||
double *umin, double *umax)
|
||||
{
|
||||
SBezier *sb;
|
||||
for(sb = l.First(); sb; sb = l.NextAfter(sb)) {
|
||||
sb->GetBoundingProjd(u, orig, umin, umax);
|
||||
}
|
||||
}
|
||||
|
||||
void SBezierLoop::MakePwlInto(SContour *sc) {
|
||||
List<Vector> lv;
|
||||
ZERO(&lv);
|
||||
|
||||
int i, j;
|
||||
for(i = 0; i < l.n; i++) {
|
||||
SBezier *sb = &(l.elem[i]);
|
||||
sb->MakePwlInto(&lv);
|
||||
|
||||
// Each curve's piecewise linearization includes its endpoints,
|
||||
// which we don't want to duplicate (creating zero-len edges).
|
||||
for(j = (i == 0 ? 0 : 1); j < lv.n; j++) {
|
||||
sc->AddPoint(lv.elem[j]);
|
||||
}
|
||||
lv.Clear();
|
||||
}
|
||||
// Ensure that it's exactly closed, not just within a numerical tolerance.
|
||||
sc->l.elem[sc->l.n - 1] = sc->l.elem[0];
|
||||
}
|
||||
|
||||
|
||||
SBezierLoopSet SBezierLoopSet::From(SBezierList *sbl, SPolygon *poly,
|
||||
bool *allClosed, SEdge *errorAt)
|
||||
{
|
||||
int i;
|
||||
SBezierLoopSet ret;
|
||||
ZERO(&ret);
|
||||
|
||||
while(sbl->l.n > 0) {
|
||||
bool thisClosed;
|
||||
SBezierLoop loop;
|
||||
loop = SBezierLoop::FromCurves(sbl, &thisClosed, errorAt);
|
||||
if(!thisClosed) {
|
||||
ret.Clear();
|
||||
*allClosed = false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret.l.Add(&loop);
|
||||
poly->AddEmptyContour();
|
||||
loop.MakePwlInto(&(poly->l.elem[poly->l.n-1]));
|
||||
}
|
||||
|
||||
poly->normal = poly->ComputeNormal();
|
||||
ret.normal = poly->normal;
|
||||
if(poly->l.n > 0) {
|
||||
ret.point = poly->AnyPoint();
|
||||
} else {
|
||||
ret.point = Vector::From(0, 0, 0);
|
||||
}
|
||||
poly->FixContourDirections();
|
||||
|
||||
for(i = 0; i < poly->l.n; i++) {
|
||||
if(poly->l.elem[i].tag) {
|
||||
// We had to reverse this contour in order to fix the poly
|
||||
// contour directions; so need to do the same with the curves.
|
||||
ret.l.elem[i].Reverse();
|
||||
}
|
||||
}
|
||||
|
||||
*allClosed = true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SBezierLoopSet::GetBoundingProjd(Vector u, Vector orig,
|
||||
double *umin, double *umax)
|
||||
{
|
||||
SBezierLoop *sbl;
|
||||
for(sbl = l.First(); sbl; sbl = l.NextAfter(sbl)) {
|
||||
sbl->GetBoundingProjd(u, orig, umin, umax);
|
||||
}
|
||||
}
|
||||
|
||||
void SBezierLoopSet::Clear(void) {
|
||||
int i;
|
||||
for(i = 0; i < l.n; i++) {
|
||||
(l.elem[i]).Clear();
|
||||
}
|
||||
l.Clear();
|
||||
}
|
||||
|
||||
SCurve SCurve::FromTransformationOf(SCurve *a, Vector t, Quaternion q) {
|
||||
SCurve ret;
|
||||
ZERO(&ret);
|
||||
|
||||
ret.h = a->h;
|
||||
ret.isExact = a->isExact;
|
||||
ret.exact = (a->exact).TransformedBy(t, q);
|
||||
ret.surfA = a->surfA;
|
||||
ret.surfB = a->surfB;
|
||||
|
||||
Vector *p;
|
||||
for(p = a->pts.First(); p; p = a->pts.NextAfter(p)) {
|
||||
Vector pp = (q.Rotate(*p)).Plus(t);
|
||||
ret.pts.Add(&pp);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SCurve::Clear(void) {
|
||||
pts.Clear();
|
||||
}
|
||||
|
||||
STrimBy STrimBy::EntireCurve(SShell *shell, hSCurve hsc, bool backwards) {
|
||||
STrimBy stb;
|
||||
ZERO(&stb);
|
||||
stb.curve = hsc;
|
||||
SCurve *sc = shell->curve.FindById(hsc);
|
||||
|
||||
if(backwards) {
|
||||
stb.finish = sc->pts.elem[0];
|
||||
stb.start = sc->pts.elem[sc->pts.n - 1];
|
||||
stb.backwards = true;
|
||||
} else {
|
||||
stb.start = sc->pts.elem[0];
|
||||
stb.finish = sc->pts.elem[sc->pts.n - 1];
|
||||
stb.backwards = false;
|
||||
}
|
||||
|
||||
return stb;
|
||||
}
|
||||
|
||||
SSurface SSurface::FromExtrusionOf(SBezier *sb, Vector t0, Vector t1) {
|
||||
SSurface ret;
|
||||
ZERO(&ret);
|
||||
|
||||
ret.degm = sb->deg;
|
||||
ret.degn = 1;
|
||||
|
||||
int i;
|
||||
for(i = 0; i <= ret.degm; i++) {
|
||||
ret.ctrl[i][0] = (sb->ctrl[i]).Plus(t0);
|
||||
ret.weight[i][0] = sb->weight[i];
|
||||
|
||||
ret.ctrl[i][1] = (sb->ctrl[i]).Plus(t1);
|
||||
ret.weight[i][1] = sb->weight[i];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool SSurface::IsExtrusion(SBezier *of, Vector *alongp) {
|
||||
int i;
|
||||
|
||||
if(degn != 1) return false;
|
||||
|
||||
Vector along = (ctrl[0][1]).Minus(ctrl[0][0]);
|
||||
for(i = 0; i <= degm; i++) {
|
||||
if((fabs(weight[i][1] - weight[i][0]) < LENGTH_EPS) &&
|
||||
((ctrl[i][1]).Minus(ctrl[i][0])).Equals(along))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// yes, we are a surface of extrusion; copy the original curve and return
|
||||
if(of) {
|
||||
for(i = 0; i <= degm; i++) {
|
||||
of->weight[i] = weight[i][0];
|
||||
of->ctrl[i] = ctrl[i][0];
|
||||
}
|
||||
of->deg = degm;
|
||||
*alongp = along;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SSurface::IsCylinder(Vector *center, Vector *axis, double *r,
|
||||
Vector *start, Vector *finish)
|
||||
{
|
||||
SBezier sb;
|
||||
if(!IsExtrusion(&sb, axis)) return false;
|
||||
if(sb.deg != 2) return false;
|
||||
|
||||
Vector t0 = (sb.ctrl[0]).Minus(sb.ctrl[1]),
|
||||
t2 = (sb.ctrl[2]).Minus(sb.ctrl[1]),
|
||||
r0 = axis->Cross(t0),
|
||||
r2 = axis->Cross(t2);
|
||||
|
||||
*center = Vector::AtIntersectionOfLines(sb.ctrl[0], (sb.ctrl[0]).Plus(r0),
|
||||
sb.ctrl[2], (sb.ctrl[2]).Plus(r2),
|
||||
NULL, NULL, NULL);
|
||||
|
||||
double rd0 = center->Minus(sb.ctrl[0]).Magnitude(),
|
||||
rd2 = center->Minus(sb.ctrl[2]).Magnitude();
|
||||
if(fabs(rd0 - rd2) > LENGTH_EPS) {
|
||||
return false;
|
||||
}
|
||||
*r = rd0;
|
||||
|
||||
Vector u = r0.WithMagnitude(1),
|
||||
v = (axis->Cross(u)).WithMagnitude(1);
|
||||
Point2d c2 = center->Project2d(u, v),
|
||||
pa2 = (sb.ctrl[0]).Project2d(u, v).Minus(c2),
|
||||
pb2 = (sb.ctrl[2]).Project2d(u, v).Minus(c2);
|
||||
|
||||
double thetaa = atan2(pa2.y, pa2.x), // in fact always zero due to csys
|
||||
thetab = atan2(pb2.y, pb2.x),
|
||||
dtheta = WRAP_NOT_0(thetab - thetaa, 2*PI);
|
||||
if(dtheta > PI) {
|
||||
// Not possible with a second order Bezier arc; so we must have
|
||||
// the points backwards.
|
||||
dtheta = 2*PI - dtheta;
|
||||
}
|
||||
|
||||
if(fabs(sb.weight[1] - cos(dtheta/2)) > LENGTH_EPS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*start = sb.ctrl[0];
|
||||
*finish = sb.ctrl[2];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SSurface SSurface::FromPlane(Vector pt, Vector u, Vector v) {
|
||||
SSurface ret;
|
||||
ZERO(&ret);
|
||||
|
||||
ret.degm = 1;
|
||||
ret.degn = 1;
|
||||
|
||||
ret.weight[0][0] = ret.weight[0][1] = 1;
|
||||
ret.weight[1][0] = ret.weight[1][1] = 1;
|
||||
|
||||
ret.ctrl[0][0] = pt;
|
||||
ret.ctrl[0][1] = pt.Plus(u);
|
||||
ret.ctrl[1][0] = pt.Plus(v);
|
||||
ret.ctrl[1][1] = pt.Plus(v).Plus(u);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
SSurface SSurface::FromTransformationOf(SSurface *a, Vector t, Quaternion q,
|
||||
bool includingTrims)
|
||||
{
|
||||
SSurface ret;
|
||||
ZERO(&ret);
|
||||
|
||||
ret.h = a->h;
|
||||
ret.color = a->color;
|
||||
ret.face = a->face;
|
||||
|
||||
ret.degm = a->degm;
|
||||
ret.degn = a->degn;
|
||||
int i, j;
|
||||
for(i = 0; i <= 3; i++) {
|
||||
for(j = 0; j <= 3; j++) {
|
||||
ret.ctrl[i][j] = (q.Rotate(a->ctrl[i][j])).Plus(t);
|
||||
ret.weight[i][j] = a->weight[i][j];
|
||||
}
|
||||
}
|
||||
|
||||
if(includingTrims) {
|
||||
STrimBy *stb;
|
||||
for(stb = a->trim.First(); stb; stb = a->trim.NextAfter(stb)) {
|
||||
STrimBy n = *stb;
|
||||
n.start = (q.Rotate(n.start)) .Plus(t);
|
||||
n.finish = (q.Rotate(n.finish)).Plus(t);
|
||||
ret.trim.Add(&n);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Vector SSurface::PointAt(double u, double v) {
|
||||
Vector num = Vector::From(0, 0, 0);
|
||||
double den = 0;
|
||||
|
@ -752,313 +328,3 @@ void SSurface::PointOnSurfaces(SSurface *s1, SSurface *s2,
|
|||
dbp("didn't converge (three surfaces intersecting)");
|
||||
}
|
||||
|
||||
void SSurface::GetAxisAlignedBounding(Vector *ptMax, Vector *ptMin) {
|
||||
*ptMax = Vector::From(VERY_NEGATIVE, VERY_NEGATIVE, VERY_NEGATIVE);
|
||||
*ptMin = Vector::From(VERY_POSITIVE, VERY_POSITIVE, VERY_POSITIVE);
|
||||
|
||||
int i, j;
|
||||
for(i = 0; i <= degm; i++) {
|
||||
for(j = 0; j <= degn; j++) {
|
||||
(ctrl[i][j]).MakeMaxMin(ptMax, ptMin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SSurface::LineEntirelyOutsideBbox(Vector a, Vector b, bool segment) {
|
||||
Vector amax, amin;
|
||||
GetAxisAlignedBounding(&amax, &amin);
|
||||
if(!Vector::BoundingBoxIntersectsLine(amax, amin, a, b, segment)) {
|
||||
// The line segment could fail to intersect the bbox, but lie entirely
|
||||
// within it and intersect the surface.
|
||||
if(a.OutsideAndNotOn(amax, amin) && b.OutsideAndNotOn(amax, amin)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SSurface::MakeEdgesInto(SShell *shell, SEdgeList *sel, bool asUv,
|
||||
SShell *useCurvesFrom) {
|
||||
STrimBy *stb;
|
||||
for(stb = trim.First(); stb; stb = trim.NextAfter(stb)) {
|
||||
SCurve *sc = shell->curve.FindById(stb->curve);
|
||||
|
||||
// We have the option to use the curves from another shell; this
|
||||
// is relevant when generating the coincident edges while doing the
|
||||
// Booleans, since the curves from the output shell will be split
|
||||
// against any intersecting surfaces (and the originals aren't).
|
||||
if(useCurvesFrom) {
|
||||
sc = useCurvesFrom->curve.FindById(sc->newH);
|
||||
}
|
||||
|
||||
Vector prev, prevuv, ptuv;
|
||||
bool inCurve = false, empty = true;
|
||||
double u = 0, v = 0;
|
||||
|
||||
int i, first, last, increment;
|
||||
if(stb->backwards) {
|
||||
first = sc->pts.n - 1;
|
||||
last = 0;
|
||||
increment = -1;
|
||||
} else {
|
||||
first = 0;
|
||||
last = sc->pts.n - 1;
|
||||
increment = 1;
|
||||
}
|
||||
for(i = first; i != (last + increment); i += increment) {
|
||||
Vector *pt = &(sc->pts.elem[i]);
|
||||
if(asUv) {
|
||||
ClosestPointTo(*pt, &u, &v);
|
||||
ptuv = Vector::From(u, v, 0);
|
||||
if(inCurve) {
|
||||
sel->AddEdge(prevuv, ptuv, sc->h.v, stb->backwards);
|
||||
empty = false;
|
||||
}
|
||||
prevuv = ptuv;
|
||||
} else {
|
||||
if(inCurve) {
|
||||
sel->AddEdge(prev, *pt, sc->h.v, stb->backwards);
|
||||
empty = false;
|
||||
}
|
||||
prev = *pt;
|
||||
}
|
||||
|
||||
if(pt->Equals(stb->start)) inCurve = true;
|
||||
if(pt->Equals(stb->finish)) inCurve = false;
|
||||
}
|
||||
if(inCurve) dbp("trim was unterminated");
|
||||
if(empty) dbp("trim was empty");
|
||||
}
|
||||
}
|
||||
|
||||
void SSurface::TriangulateInto(SShell *shell, SMesh *sm) {
|
||||
SEdgeList el;
|
||||
ZERO(&el);
|
||||
|
||||
MakeEdgesInto(shell, &el, true);
|
||||
|
||||
SPolygon poly;
|
||||
ZERO(&poly);
|
||||
if(el.AssemblePolygon(&poly, NULL, true)) {
|
||||
int i, start = sm->l.n;
|
||||
// Curved surfaces are triangulated in such a way as to minimize
|
||||
// deviation between edges and surface; but doesn't matter for planes.
|
||||
poly.UvTriangulateInto(sm, (degm == 1 && degn == 1) ? NULL : this);
|
||||
|
||||
STriMeta meta = { face, color };
|
||||
for(i = start; i < sm->l.n; i++) {
|
||||
STriangle *st = &(sm->l.elem[i]);
|
||||
st->meta = meta;
|
||||
st->an = NormalAt(st->a.x, st->a.y);
|
||||
st->bn = NormalAt(st->b.x, st->b.y);
|
||||
st->cn = NormalAt(st->c.x, st->c.y);
|
||||
st->a = PointAt(st->a.x, st->a.y);
|
||||
st->b = PointAt(st->b.x, st->b.y);
|
||||
st->c = PointAt(st->c.x, st->c.y);
|
||||
// Works out that my chosen contour direction is inconsistent with
|
||||
// the triangle direction, sigh.
|
||||
st->FlipNormal();
|
||||
}
|
||||
} else {
|
||||
dbp("failed to assemble polygon to trim nurbs surface in uv space");
|
||||
}
|
||||
|
||||
el.Clear();
|
||||
poly.Clear();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Reverse the parametrisation of one of our dimensions, which flips the
|
||||
// normal. We therefore must reverse all our trim curves too. The uv
|
||||
// coordinates change, but trim curves are stored as xyz so nothing happens
|
||||
//-----------------------------------------------------------------------------
|
||||
void SSurface::Reverse(void) {
|
||||
int i, j;
|
||||
for(i = 0; i < (degm+1)/2; i++) {
|
||||
for(j = 0; j <= degn; j++) {
|
||||
SWAP(Vector, ctrl[i][j], ctrl[degm-i][j]);
|
||||
SWAP(double, weight[i][j], weight[degm-i][j]);
|
||||
}
|
||||
}
|
||||
|
||||
STrimBy *stb;
|
||||
for(stb = trim.First(); stb; stb = trim.NextAfter(stb)) {
|
||||
stb->backwards = !stb->backwards;
|
||||
SWAP(Vector, stb->start, stb->finish);
|
||||
}
|
||||
}
|
||||
|
||||
void SSurface::Clear(void) {
|
||||
trim.Clear();
|
||||
}
|
||||
|
||||
void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1,
|
||||
int color)
|
||||
{
|
||||
ZERO(this);
|
||||
|
||||
// Make the extrusion direction consistent with respect to the normal
|
||||
// of the sketch we're extruding.
|
||||
if((t0.Minus(t1)).Dot(sbls->normal) < 0) {
|
||||
SWAP(Vector, t0, t1);
|
||||
}
|
||||
|
||||
// Define a coordinate system to contain the original sketch, and get
|
||||
// a bounding box in that csys
|
||||
Vector n = sbls->normal.ScaledBy(-1);
|
||||
Vector u = n.Normal(0), v = n.Normal(1);
|
||||
Vector orig = sbls->point;
|
||||
double umax = 1e-10, umin = 1e10;
|
||||
sbls->GetBoundingProjd(u, orig, &umin, &umax);
|
||||
double vmax = 1e-10, vmin = 1e10;
|
||||
sbls->GetBoundingProjd(v, orig, &vmin, &vmax);
|
||||
// and now fix things up so that all u and v lie between 0 and 1
|
||||
orig = orig.Plus(u.ScaledBy(umin));
|
||||
orig = orig.Plus(v.ScaledBy(vmin));
|
||||
u = u.ScaledBy(umax - umin);
|
||||
v = v.ScaledBy(vmax - vmin);
|
||||
|
||||
// So we can now generate the top and bottom surfaces of the extrusion,
|
||||
// planes within a translated (and maybe mirrored) version of that csys.
|
||||
SSurface s0, s1;
|
||||
s0 = SSurface::FromPlane(orig.Plus(t0), u, v);
|
||||
s0.color = color;
|
||||
s1 = SSurface::FromPlane(orig.Plus(t1).Plus(u), u.ScaledBy(-1), v);
|
||||
s1.color = color;
|
||||
hSSurface hs0 = surface.AddAndAssignId(&s0),
|
||||
hs1 = surface.AddAndAssignId(&s1);
|
||||
|
||||
// Now go through the input curves. For each one, generate its surface
|
||||
// of extrusion, its two translated trim curves, and one trim line. We
|
||||
// go through by loops so that we can assign the lines correctly.
|
||||
SBezierLoop *sbl;
|
||||
for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) {
|
||||
SBezier *sb;
|
||||
|
||||
typedef struct {
|
||||
hSCurve hc;
|
||||
hSSurface hs;
|
||||
} TrimLine;
|
||||
List<TrimLine> trimLines;
|
||||
ZERO(&trimLines);
|
||||
|
||||
for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) {
|
||||
// Generate the surface of extrusion of this curve, and add
|
||||
// it to the list
|
||||
SSurface ss = SSurface::FromExtrusionOf(sb, t0, t1);
|
||||
ss.color = color;
|
||||
hSSurface hsext = surface.AddAndAssignId(&ss);
|
||||
|
||||
// Translate the curve by t0 and t1 to produce two trim curves
|
||||
SCurve sc;
|
||||
ZERO(&sc);
|
||||
sc.isExact = true;
|
||||
sc.exact = sb->TransformedBy(t0, Quaternion::IDENTITY);
|
||||
(sc.exact).MakePwlInto(&(sc.pts));
|
||||
sc.surfA = hs0;
|
||||
sc.surfB = hsext;
|
||||
hSCurve hc0 = curve.AddAndAssignId(&sc);
|
||||
|
||||
ZERO(&sc);
|
||||
sc.isExact = true;
|
||||
sc.exact = sb->TransformedBy(t1, Quaternion::IDENTITY);
|
||||
(sc.exact).MakePwlInto(&(sc.pts));
|
||||
sc.surfA = hs1;
|
||||
sc.surfB = hsext;
|
||||
hSCurve hc1 = curve.AddAndAssignId(&sc);
|
||||
|
||||
STrimBy stb0, stb1;
|
||||
// The translated curves trim the flat top and bottom surfaces.
|
||||
stb0 = STrimBy::EntireCurve(this, hc0, false);
|
||||
stb1 = STrimBy::EntireCurve(this, hc1, true);
|
||||
(surface.FindById(hs0))->trim.Add(&stb0);
|
||||
(surface.FindById(hs1))->trim.Add(&stb1);
|
||||
|
||||
// The translated curves also trim the surface of extrusion.
|
||||
stb0 = STrimBy::EntireCurve(this, hc0, true);
|
||||
stb1 = STrimBy::EntireCurve(this, hc1, false);
|
||||
(surface.FindById(hsext))->trim.Add(&stb0);
|
||||
(surface.FindById(hsext))->trim.Add(&stb1);
|
||||
|
||||
// And form the trim line
|
||||
Vector pt = sb->Finish();
|
||||
ZERO(&sc);
|
||||
sc.isExact = true;
|
||||
sc.exact = SBezier::From(pt.Plus(t0), pt.Plus(t1));
|
||||
(sc.exact).MakePwlInto(&(sc.pts));
|
||||
hSCurve hl = curve.AddAndAssignId(&sc);
|
||||
// save this for later
|
||||
TrimLine tl;
|
||||
tl.hc = hl;
|
||||
tl.hs = hsext;
|
||||
trimLines.Add(&tl);
|
||||
}
|
||||
|
||||
int i;
|
||||
for(i = 0; i < trimLines.n; i++) {
|
||||
TrimLine *tl = &(trimLines.elem[i]);
|
||||
SSurface *ss = surface.FindById(tl->hs);
|
||||
|
||||
TrimLine *tlp = &(trimLines.elem[WRAP(i-1, trimLines.n)]);
|
||||
|
||||
STrimBy stb;
|
||||
stb = STrimBy::EntireCurve(this, tl->hc, true);
|
||||
ss->trim.Add(&stb);
|
||||
stb = STrimBy::EntireCurve(this, tlp->hc, false);
|
||||
ss->trim.Add(&stb);
|
||||
|
||||
(curve.FindById(tl->hc))->surfA = ss->h;
|
||||
(curve.FindById(tlp->hc))->surfB = ss->h;
|
||||
}
|
||||
trimLines.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
void SShell::MakeFromCopyOf(SShell *a) {
|
||||
MakeFromTransformationOf(a, Vector::From(0, 0, 0), Quaternion::IDENTITY);
|
||||
}
|
||||
|
||||
void SShell::MakeFromTransformationOf(SShell *a, Vector t, Quaternion q) {
|
||||
SSurface *s;
|
||||
for(s = a->surface.First(); s; s = a->surface.NextAfter(s)) {
|
||||
SSurface n;
|
||||
n = SSurface::FromTransformationOf(s, t, q, true);
|
||||
surface.Add(&n); // keeping the old ID
|
||||
}
|
||||
|
||||
SCurve *c;
|
||||
for(c = a->curve.First(); c; c = a->curve.NextAfter(c)) {
|
||||
SCurve n;
|
||||
n = SCurve::FromTransformationOf(c, t, q);
|
||||
curve.Add(&n); // keeping the old ID
|
||||
}
|
||||
}
|
||||
|
||||
void SShell::MakeEdgesInto(SEdgeList *sel) {
|
||||
SSurface *s;
|
||||
for(s = surface.First(); s; s = surface.NextAfter(s)) {
|
||||
s->MakeEdgesInto(this, sel, false);
|
||||
}
|
||||
}
|
||||
|
||||
void SShell::TriangulateInto(SMesh *sm) {
|
||||
SSurface *s;
|
||||
for(s = surface.First(); s; s = surface.NextAfter(s)) {
|
||||
s->TriangulateInto(this, sm);
|
||||
}
|
||||
}
|
||||
|
||||
void SShell::Clear(void) {
|
||||
SSurface *s;
|
||||
for(s = surface.First(); s; s = surface.NextAfter(s)) {
|
||||
s->Clear();
|
||||
}
|
||||
surface.Clear();
|
||||
|
||||
SCurve *c;
|
||||
for(c = curve.First(); c; c = curve.NextAfter(c)) {
|
||||
c->Clear();
|
||||
}
|
||||
curve.Clear();
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,462 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Anything involving surfaces and sets of surfaces (i.e., shells); except
|
||||
// for the real math, which is in ratpoly.cpp.
|
||||
//-----------------------------------------------------------------------------
|
||||
#include "../solvespace.h"
|
||||
|
||||
SSurface SSurface::FromExtrusionOf(SBezier *sb, Vector t0, Vector t1) {
|
||||
SSurface ret;
|
||||
ZERO(&ret);
|
||||
|
||||
ret.degm = sb->deg;
|
||||
ret.degn = 1;
|
||||
|
||||
int i;
|
||||
for(i = 0; i <= ret.degm; i++) {
|
||||
ret.ctrl[i][0] = (sb->ctrl[i]).Plus(t0);
|
||||
ret.weight[i][0] = sb->weight[i];
|
||||
|
||||
ret.ctrl[i][1] = (sb->ctrl[i]).Plus(t1);
|
||||
ret.weight[i][1] = sb->weight[i];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool SSurface::IsExtrusion(SBezier *of, Vector *alongp) {
|
||||
int i;
|
||||
|
||||
if(degn != 1) return false;
|
||||
|
||||
Vector along = (ctrl[0][1]).Minus(ctrl[0][0]);
|
||||
for(i = 0; i <= degm; i++) {
|
||||
if((fabs(weight[i][1] - weight[i][0]) < LENGTH_EPS) &&
|
||||
((ctrl[i][1]).Minus(ctrl[i][0])).Equals(along))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// yes, we are a surface of extrusion; copy the original curve and return
|
||||
if(of) {
|
||||
for(i = 0; i <= degm; i++) {
|
||||
of->weight[i] = weight[i][0];
|
||||
of->ctrl[i] = ctrl[i][0];
|
||||
}
|
||||
of->deg = degm;
|
||||
*alongp = along;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SSurface::IsCylinder(Vector *center, Vector *axis, double *r,
|
||||
Vector *start, Vector *finish)
|
||||
{
|
||||
SBezier sb;
|
||||
if(!IsExtrusion(&sb, axis)) return false;
|
||||
if(sb.deg != 2) return false;
|
||||
|
||||
Vector t0 = (sb.ctrl[0]).Minus(sb.ctrl[1]),
|
||||
t2 = (sb.ctrl[2]).Minus(sb.ctrl[1]),
|
||||
r0 = axis->Cross(t0),
|
||||
r2 = axis->Cross(t2);
|
||||
|
||||
*center = Vector::AtIntersectionOfLines(sb.ctrl[0], (sb.ctrl[0]).Plus(r0),
|
||||
sb.ctrl[2], (sb.ctrl[2]).Plus(r2),
|
||||
NULL, NULL, NULL);
|
||||
|
||||
double rd0 = center->Minus(sb.ctrl[0]).Magnitude(),
|
||||
rd2 = center->Minus(sb.ctrl[2]).Magnitude();
|
||||
if(fabs(rd0 - rd2) > LENGTH_EPS) {
|
||||
return false;
|
||||
}
|
||||
*r = rd0;
|
||||
|
||||
Vector u = r0.WithMagnitude(1),
|
||||
v = (axis->Cross(u)).WithMagnitude(1);
|
||||
Point2d c2 = center->Project2d(u, v),
|
||||
pa2 = (sb.ctrl[0]).Project2d(u, v).Minus(c2),
|
||||
pb2 = (sb.ctrl[2]).Project2d(u, v).Minus(c2);
|
||||
|
||||
double thetaa = atan2(pa2.y, pa2.x), // in fact always zero due to csys
|
||||
thetab = atan2(pb2.y, pb2.x),
|
||||
dtheta = WRAP_NOT_0(thetab - thetaa, 2*PI);
|
||||
if(dtheta > PI) {
|
||||
// Not possible with a second order Bezier arc; so we must have
|
||||
// the points backwards.
|
||||
dtheta = 2*PI - dtheta;
|
||||
}
|
||||
|
||||
if(fabs(sb.weight[1] - cos(dtheta/2)) > LENGTH_EPS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*start = sb.ctrl[0];
|
||||
*finish = sb.ctrl[2];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SSurface SSurface::FromPlane(Vector pt, Vector u, Vector v) {
|
||||
SSurface ret;
|
||||
ZERO(&ret);
|
||||
|
||||
ret.degm = 1;
|
||||
ret.degn = 1;
|
||||
|
||||
ret.weight[0][0] = ret.weight[0][1] = 1;
|
||||
ret.weight[1][0] = ret.weight[1][1] = 1;
|
||||
|
||||
ret.ctrl[0][0] = pt;
|
||||
ret.ctrl[0][1] = pt.Plus(u);
|
||||
ret.ctrl[1][0] = pt.Plus(v);
|
||||
ret.ctrl[1][1] = pt.Plus(v).Plus(u);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
SSurface SSurface::FromTransformationOf(SSurface *a, Vector t, Quaternion q,
|
||||
bool includingTrims)
|
||||
{
|
||||
SSurface ret;
|
||||
ZERO(&ret);
|
||||
|
||||
ret.h = a->h;
|
||||
ret.color = a->color;
|
||||
ret.face = a->face;
|
||||
|
||||
ret.degm = a->degm;
|
||||
ret.degn = a->degn;
|
||||
int i, j;
|
||||
for(i = 0; i <= 3; i++) {
|
||||
for(j = 0; j <= 3; j++) {
|
||||
ret.ctrl[i][j] = (q.Rotate(a->ctrl[i][j])).Plus(t);
|
||||
ret.weight[i][j] = a->weight[i][j];
|
||||
}
|
||||
}
|
||||
|
||||
if(includingTrims) {
|
||||
STrimBy *stb;
|
||||
for(stb = a->trim.First(); stb; stb = a->trim.NextAfter(stb)) {
|
||||
STrimBy n = *stb;
|
||||
n.start = (q.Rotate(n.start)) .Plus(t);
|
||||
n.finish = (q.Rotate(n.finish)).Plus(t);
|
||||
ret.trim.Add(&n);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SSurface::GetAxisAlignedBounding(Vector *ptMax, Vector *ptMin) {
|
||||
*ptMax = Vector::From(VERY_NEGATIVE, VERY_NEGATIVE, VERY_NEGATIVE);
|
||||
*ptMin = Vector::From(VERY_POSITIVE, VERY_POSITIVE, VERY_POSITIVE);
|
||||
|
||||
int i, j;
|
||||
for(i = 0; i <= degm; i++) {
|
||||
for(j = 0; j <= degn; j++) {
|
||||
(ctrl[i][j]).MakeMaxMin(ptMax, ptMin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SSurface::LineEntirelyOutsideBbox(Vector a, Vector b, bool segment) {
|
||||
Vector amax, amin;
|
||||
GetAxisAlignedBounding(&amax, &amin);
|
||||
if(!Vector::BoundingBoxIntersectsLine(amax, amin, a, b, segment)) {
|
||||
// The line segment could fail to intersect the bbox, but lie entirely
|
||||
// within it and intersect the surface.
|
||||
if(a.OutsideAndNotOn(amax, amin) && b.OutsideAndNotOn(amax, amin)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SSurface::MakeEdgesInto(SShell *shell, SEdgeList *sel, bool asUv,
|
||||
SShell *useCurvesFrom)
|
||||
{
|
||||
STrimBy *stb;
|
||||
for(stb = trim.First(); stb; stb = trim.NextAfter(stb)) {
|
||||
SCurve *sc = shell->curve.FindById(stb->curve);
|
||||
|
||||
// We have the option to use the curves from another shell; this
|
||||
// is relevant when generating the coincident edges while doing the
|
||||
// Booleans, since the curves from the output shell will be split
|
||||
// against any intersecting surfaces (and the originals aren't).
|
||||
if(useCurvesFrom) {
|
||||
sc = useCurvesFrom->curve.FindById(sc->newH);
|
||||
}
|
||||
|
||||
Vector prev, prevuv, ptuv;
|
||||
bool inCurve = false, empty = true;
|
||||
double u = 0, v = 0;
|
||||
|
||||
int i, first, last, increment;
|
||||
if(stb->backwards) {
|
||||
first = sc->pts.n - 1;
|
||||
last = 0;
|
||||
increment = -1;
|
||||
} else {
|
||||
first = 0;
|
||||
last = sc->pts.n - 1;
|
||||
increment = 1;
|
||||
}
|
||||
for(i = first; i != (last + increment); i += increment) {
|
||||
Vector *pt = &(sc->pts.elem[i]);
|
||||
if(asUv) {
|
||||
ClosestPointTo(*pt, &u, &v);
|
||||
ptuv = Vector::From(u, v, 0);
|
||||
if(inCurve) {
|
||||
sel->AddEdge(prevuv, ptuv, sc->h.v, stb->backwards);
|
||||
empty = false;
|
||||
}
|
||||
prevuv = ptuv;
|
||||
} else {
|
||||
if(inCurve) {
|
||||
sel->AddEdge(prev, *pt, sc->h.v, stb->backwards);
|
||||
empty = false;
|
||||
}
|
||||
prev = *pt;
|
||||
}
|
||||
|
||||
if(pt->Equals(stb->start)) inCurve = true;
|
||||
if(pt->Equals(stb->finish)) inCurve = false;
|
||||
}
|
||||
if(inCurve) dbp("trim was unterminated");
|
||||
if(empty) dbp("trim was empty");
|
||||
}
|
||||
}
|
||||
|
||||
void SSurface::TriangulateInto(SShell *shell, SMesh *sm) {
|
||||
SEdgeList el;
|
||||
ZERO(&el);
|
||||
|
||||
MakeEdgesInto(shell, &el, true);
|
||||
|
||||
SPolygon poly;
|
||||
ZERO(&poly);
|
||||
if(el.AssemblePolygon(&poly, NULL, true)) {
|
||||
int i, start = sm->l.n;
|
||||
// Curved surfaces are triangulated in such a way as to minimize
|
||||
// deviation between edges and surface; but doesn't matter for planes.
|
||||
poly.UvTriangulateInto(sm, (degm == 1 && degn == 1) ? NULL : this);
|
||||
|
||||
STriMeta meta = { face, color };
|
||||
for(i = start; i < sm->l.n; i++) {
|
||||
STriangle *st = &(sm->l.elem[i]);
|
||||
st->meta = meta;
|
||||
st->an = NormalAt(st->a.x, st->a.y);
|
||||
st->bn = NormalAt(st->b.x, st->b.y);
|
||||
st->cn = NormalAt(st->c.x, st->c.y);
|
||||
st->a = PointAt(st->a.x, st->a.y);
|
||||
st->b = PointAt(st->b.x, st->b.y);
|
||||
st->c = PointAt(st->c.x, st->c.y);
|
||||
// Works out that my chosen contour direction is inconsistent with
|
||||
// the triangle direction, sigh.
|
||||
st->FlipNormal();
|
||||
}
|
||||
} else {
|
||||
dbp("failed to assemble polygon to trim nurbs surface in uv space");
|
||||
}
|
||||
|
||||
el.Clear();
|
||||
poly.Clear();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Reverse the parametrisation of one of our dimensions, which flips the
|
||||
// normal. We therefore must reverse all our trim curves too. The uv
|
||||
// coordinates change, but trim curves are stored as xyz so nothing happens
|
||||
//-----------------------------------------------------------------------------
|
||||
void SSurface::Reverse(void) {
|
||||
int i, j;
|
||||
for(i = 0; i < (degm+1)/2; i++) {
|
||||
for(j = 0; j <= degn; j++) {
|
||||
SWAP(Vector, ctrl[i][j], ctrl[degm-i][j]);
|
||||
SWAP(double, weight[i][j], weight[degm-i][j]);
|
||||
}
|
||||
}
|
||||
|
||||
STrimBy *stb;
|
||||
for(stb = trim.First(); stb; stb = trim.NextAfter(stb)) {
|
||||
stb->backwards = !stb->backwards;
|
||||
SWAP(Vector, stb->start, stb->finish);
|
||||
}
|
||||
}
|
||||
|
||||
void SSurface::Clear(void) {
|
||||
trim.Clear();
|
||||
}
|
||||
|
||||
void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1,
|
||||
int color)
|
||||
{
|
||||
ZERO(this);
|
||||
|
||||
// Make the extrusion direction consistent with respect to the normal
|
||||
// of the sketch we're extruding.
|
||||
if((t0.Minus(t1)).Dot(sbls->normal) < 0) {
|
||||
SWAP(Vector, t0, t1);
|
||||
}
|
||||
|
||||
// Define a coordinate system to contain the original sketch, and get
|
||||
// a bounding box in that csys
|
||||
Vector n = sbls->normal.ScaledBy(-1);
|
||||
Vector u = n.Normal(0), v = n.Normal(1);
|
||||
Vector orig = sbls->point;
|
||||
double umax = 1e-10, umin = 1e10;
|
||||
sbls->GetBoundingProjd(u, orig, &umin, &umax);
|
||||
double vmax = 1e-10, vmin = 1e10;
|
||||
sbls->GetBoundingProjd(v, orig, &vmin, &vmax);
|
||||
// and now fix things up so that all u and v lie between 0 and 1
|
||||
orig = orig.Plus(u.ScaledBy(umin));
|
||||
orig = orig.Plus(v.ScaledBy(vmin));
|
||||
u = u.ScaledBy(umax - umin);
|
||||
v = v.ScaledBy(vmax - vmin);
|
||||
|
||||
// So we can now generate the top and bottom surfaces of the extrusion,
|
||||
// planes within a translated (and maybe mirrored) version of that csys.
|
||||
SSurface s0, s1;
|
||||
s0 = SSurface::FromPlane(orig.Plus(t0), u, v);
|
||||
s0.color = color;
|
||||
s1 = SSurface::FromPlane(orig.Plus(t1).Plus(u), u.ScaledBy(-1), v);
|
||||
s1.color = color;
|
||||
hSSurface hs0 = surface.AddAndAssignId(&s0),
|
||||
hs1 = surface.AddAndAssignId(&s1);
|
||||
|
||||
// Now go through the input curves. For each one, generate its surface
|
||||
// of extrusion, its two translated trim curves, and one trim line. We
|
||||
// go through by loops so that we can assign the lines correctly.
|
||||
SBezierLoop *sbl;
|
||||
for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) {
|
||||
SBezier *sb;
|
||||
|
||||
typedef struct {
|
||||
hSCurve hc;
|
||||
hSSurface hs;
|
||||
} TrimLine;
|
||||
List<TrimLine> trimLines;
|
||||
ZERO(&trimLines);
|
||||
|
||||
for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) {
|
||||
// Generate the surface of extrusion of this curve, and add
|
||||
// it to the list
|
||||
SSurface ss = SSurface::FromExtrusionOf(sb, t0, t1);
|
||||
ss.color = color;
|
||||
hSSurface hsext = surface.AddAndAssignId(&ss);
|
||||
|
||||
// Translate the curve by t0 and t1 to produce two trim curves
|
||||
SCurve sc;
|
||||
ZERO(&sc);
|
||||
sc.isExact = true;
|
||||
sc.exact = sb->TransformedBy(t0, Quaternion::IDENTITY);
|
||||
(sc.exact).MakePwlInto(&(sc.pts));
|
||||
sc.surfA = hs0;
|
||||
sc.surfB = hsext;
|
||||
hSCurve hc0 = curve.AddAndAssignId(&sc);
|
||||
|
||||
ZERO(&sc);
|
||||
sc.isExact = true;
|
||||
sc.exact = sb->TransformedBy(t1, Quaternion::IDENTITY);
|
||||
(sc.exact).MakePwlInto(&(sc.pts));
|
||||
sc.surfA = hs1;
|
||||
sc.surfB = hsext;
|
||||
hSCurve hc1 = curve.AddAndAssignId(&sc);
|
||||
|
||||
STrimBy stb0, stb1;
|
||||
// The translated curves trim the flat top and bottom surfaces.
|
||||
stb0 = STrimBy::EntireCurve(this, hc0, false);
|
||||
stb1 = STrimBy::EntireCurve(this, hc1, true);
|
||||
(surface.FindById(hs0))->trim.Add(&stb0);
|
||||
(surface.FindById(hs1))->trim.Add(&stb1);
|
||||
|
||||
// The translated curves also trim the surface of extrusion.
|
||||
stb0 = STrimBy::EntireCurve(this, hc0, true);
|
||||
stb1 = STrimBy::EntireCurve(this, hc1, false);
|
||||
(surface.FindById(hsext))->trim.Add(&stb0);
|
||||
(surface.FindById(hsext))->trim.Add(&stb1);
|
||||
|
||||
// And form the trim line
|
||||
Vector pt = sb->Finish();
|
||||
ZERO(&sc);
|
||||
sc.isExact = true;
|
||||
sc.exact = SBezier::From(pt.Plus(t0), pt.Plus(t1));
|
||||
(sc.exact).MakePwlInto(&(sc.pts));
|
||||
hSCurve hl = curve.AddAndAssignId(&sc);
|
||||
// save this for later
|
||||
TrimLine tl;
|
||||
tl.hc = hl;
|
||||
tl.hs = hsext;
|
||||
trimLines.Add(&tl);
|
||||
}
|
||||
|
||||
int i;
|
||||
for(i = 0; i < trimLines.n; i++) {
|
||||
TrimLine *tl = &(trimLines.elem[i]);
|
||||
SSurface *ss = surface.FindById(tl->hs);
|
||||
|
||||
TrimLine *tlp = &(trimLines.elem[WRAP(i-1, trimLines.n)]);
|
||||
|
||||
STrimBy stb;
|
||||
stb = STrimBy::EntireCurve(this, tl->hc, true);
|
||||
ss->trim.Add(&stb);
|
||||
stb = STrimBy::EntireCurve(this, tlp->hc, false);
|
||||
ss->trim.Add(&stb);
|
||||
|
||||
(curve.FindById(tl->hc))->surfA = ss->h;
|
||||
(curve.FindById(tlp->hc))->surfB = ss->h;
|
||||
}
|
||||
trimLines.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
void SShell::MakeFromCopyOf(SShell *a) {
|
||||
MakeFromTransformationOf(a, Vector::From(0, 0, 0), Quaternion::IDENTITY);
|
||||
}
|
||||
|
||||
void SShell::MakeFromTransformationOf(SShell *a, Vector t, Quaternion q) {
|
||||
SSurface *s;
|
||||
for(s = a->surface.First(); s; s = a->surface.NextAfter(s)) {
|
||||
SSurface n;
|
||||
n = SSurface::FromTransformationOf(s, t, q, true);
|
||||
surface.Add(&n); // keeping the old ID
|
||||
}
|
||||
|
||||
SCurve *c;
|
||||
for(c = a->curve.First(); c; c = a->curve.NextAfter(c)) {
|
||||
SCurve n;
|
||||
n = SCurve::FromTransformationOf(c, t, q);
|
||||
curve.Add(&n); // keeping the old ID
|
||||
}
|
||||
}
|
||||
|
||||
void SShell::MakeEdgesInto(SEdgeList *sel) {
|
||||
SSurface *s;
|
||||
for(s = surface.First(); s; s = surface.NextAfter(s)) {
|
||||
s->MakeEdgesInto(this, sel, false);
|
||||
}
|
||||
}
|
||||
|
||||
void SShell::TriangulateInto(SMesh *sm) {
|
||||
SSurface *s;
|
||||
for(s = surface.First(); s; s = surface.NextAfter(s)) {
|
||||
s->TriangulateInto(this, sm);
|
||||
}
|
||||
}
|
||||
|
||||
void SShell::Clear(void) {
|
||||
SSurface *s;
|
||||
for(s = surface.First(); s; s = surface.NextAfter(s)) {
|
||||
s->Clear();
|
||||
}
|
||||
surface.Clear();
|
||||
|
||||
SCurve *c;
|
||||
for(c = curve.First(); c; c = curve.NextAfter(c)) {
|
||||
c->Clear();
|
||||
}
|
||||
curve.Clear();
|
||||
}
|
||||
|
|
@ -424,9 +424,6 @@ void TextWindow::ShowGroupInfo(void) {
|
|||
(asy || !asa ? "" : "assemble"), (asy && asa ? "assemble" : ""));
|
||||
}
|
||||
if(g->type == Group::IMPORTED) {
|
||||
if(g->meshError.yes) {
|
||||
Printf(false, "%Fx the parts interfere!");
|
||||
}
|
||||
bool sup = g->suppress;
|
||||
Printf(false, "%FtSUPPRESS%E %Fh%f%Ll%s%E%Fs%s%E / %Fh%f%Ll%s%E%Fs%s%E",
|
||||
&TextWindow::ScreenChangeSuppress,
|
||||
|
|
1
ui.h
1
ui.h
|
@ -246,6 +246,7 @@ public:
|
|||
MNU_COMMENT,
|
||||
// Analyze
|
||||
MNU_VOLUME,
|
||||
MNU_INTERFERENCE,
|
||||
MNU_NAKED_EDGES,
|
||||
MNU_SHOW_DOF,
|
||||
MNU_TRACE_PT,
|
||||
|
|
|
@ -53,8 +53,7 @@ void SolveSpace::PushFromCurrentOnto(UndoStack *uk) {
|
|||
ZERO(&(dest.runningMesh));
|
||||
ZERO(&(dest.thisShell));
|
||||
ZERO(&(dest.runningShell));
|
||||
ZERO(&(dest.meshError));
|
||||
ZERO(&(dest.emphEdges));
|
||||
ZERO(&(dest.runningEdges));
|
||||
|
||||
ZERO(&(dest.remap));
|
||||
src->remap.DeepCopyInto(&(dest.remap));
|
||||
|
@ -96,8 +95,7 @@ void SolveSpace::PopOntoCurrentFrom(UndoStack *uk) {
|
|||
g->runningMesh.Clear();
|
||||
g->thisShell.Clear();
|
||||
g->runningShell.Clear();
|
||||
g->meshError.interferesAt.Clear();
|
||||
g->emphEdges.Clear();
|
||||
g->runningEdges.Clear();
|
||||
g->remap.Clear();
|
||||
g->impMesh.Clear();
|
||||
g->impEntity.Clear();
|
||||
|
|
Loading…
Reference in New Issue