diff --git a/Makefile b/Makefile index c2b2068..1d906d9 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ DEFINES = /D_WIN32_WINNT=0x500 /DISOLATION_AWARE_ENABLED /D_WIN32_IE=0x500 /DWIN # 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 srf\surface.h OBJDIR = obj @@ -39,6 +39,9 @@ SSOBJS = $(OBJDIR)\solvespace.obj \ $(OBJDIR)\generate.obj \ $(OBJDIR)\export.obj \ +SRFOBJS = $(OBJDIR)\ratpoly.obj \ + + RES = $(OBJDIR)\resource.res @@ -52,14 +55,17 @@ all: $(OBJDIR)/solvespace.exe clean: rm -f obj/* -$(OBJDIR)/solvespace.exe: $(SSOBJS) $(W32OBJS) $(FREEZE) $(RES) - @$(CC) $(DEFINES) $(CFLAGS) -Fe$(OBJDIR)/solvespace.exe $(SSOBJS) $(W32OBJS) $(FREEZE) $(RES) $(LIBS) +$(OBJDIR)/solvespace.exe: $(SSOBJS) $(SRFOBJS) $(W32OBJS) $(FREEZE) $(RES) + @$(CC) $(DEFINES) $(CFLAGS) -Fe$(OBJDIR)/solvespace.exe $(SSOBJS) $(SRFOBJS) $(W32OBJS) $(FREEZE) $(RES) $(LIBS) editbin /nologo /STACK:8388608 $(OBJDIR)/solvespace.exe @echo solvespace.exe $(SSOBJS): $(@B).cpp $(HEADERS) @$(CC) $(CFLAGS) $(DEFINES) -c -Fo$(OBJDIR)/$(@B).obj $(@B).cpp +$(SRFOBJS): srf\$(@B).cpp $(HEADERS) + @$(CC) $(CFLAGS) $(DEFINES) -c -Fo$(OBJDIR)/$(@B).obj srf\$(@B).cpp + $(W32OBJS): win32/$(@B).cpp $(HEADERS) @$(CC) $(CFLAGS) $(DEFINES) -c -Fo$(OBJDIR)/$(@B).obj win32/$(@B).cpp diff --git a/drawentity.cpp b/drawentity.cpp index 5dda1e7..7828c94 100644 --- a/drawentity.cpp +++ b/drawentity.cpp @@ -21,13 +21,6 @@ void Entity::LineDrawOrGetDistance(Vector a, Vector b) { dogd.refp = (a.Plus(b)).ScaledBy(0.5); } -void Entity::LineDrawOrGetDistanceOrEdge(Vector a, Vector b) { - LineDrawOrGetDistance(a, b); - if(dogd.edges && !construction) { - dogd.edges->AddEdge(a, b); - } -} - void Entity::DrawAll(void) { // This handles points and line segments as a special case, because I // seem to be able to get a huge speedup that way, by consolidating @@ -96,20 +89,34 @@ void Entity::DrawAll(void) { void Entity::Draw(void) { dogd.drawing = true; - dogd.edges = NULL; DrawOrGetDistance(); } -void Entity::GenerateEdges(SEdgeList *el) { - dogd.drawing = false; - dogd.edges = el; - DrawOrGetDistance(); - dogd.edges = NULL; +void Entity::GenerateEdges(SEdgeList *el, bool includingConstruction) { + if(construction && !includingConstruction) return; + + SPolyCurveList spcl; + ZERO(&spcl); + GeneratePolyCurves(&spcl); + + int i, j; + for(i = 0; i < spcl.l.n; i++) { + SPolyCurve *spc = &(spcl.l.elem[i]); + + List lv; + ZERO(&lv); + spc->MakePwlInto(&lv); + for(j = 1; j < lv.n; j++) { + el->AddEdge(lv.elem[j-1], lv.elem[j]); + } + lv.Clear(); + } + + spcl.Clear(); } double Entity::GetDistance(Point2d mp) { dogd.drawing = false; - dogd.edges = NULL; dogd.mp = mp; dogd.dmin = 1e12; @@ -120,7 +127,6 @@ double Entity::GetDistance(Point2d mp) { Vector Entity::GetReferencePos(void) { dogd.drawing = false; - dogd.edges = NULL; dogd.refp = SS.GW.offset.ScaledBy(-1); DrawOrGetDistance(); @@ -157,48 +163,102 @@ bool Entity::IsVisible(void) { return true; } -Vector Entity::BezierEval(double t, Vector p0, Vector p1, Vector p2, Vector p3) -{ - return (p0.ScaledBy((1 - t)*(1 - t)*(1 - t))).Plus( - (p1.ScaledBy(3*t*(1 - t)*(1 - t))).Plus( - (p2.ScaledBy(3*t*t*(1 - t))).Plus( - (p3.ScaledBy(t*t*t))))); -} +void Entity::GeneratePolyCurves(SPolyCurveList *spcl) { + SPolyCurve spc; -void Entity::BezierPwl(double ta, double tb, - Vector p0, Vector p1, Vector p2, Vector p3) -{ - Vector pa = BezierEval(ta, p0, p1, p2, p3); - Vector pb = BezierEval(tb, p0, p1, p2, p3); + switch(type) { + case LINE_SEGMENT: { + Vector a = SS.GetEntity(point[0])->PointGetNum(); + Vector b = SS.GetEntity(point[1])->PointGetNum(); + spc = SPolyCurve::From(a, b); + spcl->l.Add(&spc); + break; + } + case CUBIC: { + Vector p0 = SS.GetEntity(point[0])->PointGetNum(); + Vector p1 = SS.GetEntity(point[1])->PointGetNum(); + Vector p2 = SS.GetEntity(point[2])->PointGetNum(); + Vector p3 = SS.GetEntity(point[3])->PointGetNum(); + spc = SPolyCurve::From(p0, p1, p2, p3); + spcl->l.Add(&spc); + break; + } - double tm1 = (2*ta + tb) / 3; - double tm2 = (ta + 2*tb) / 3; + case CIRCLE: + case ARC_OF_CIRCLE: { + Vector center = SS.GetEntity(point[0])->PointGetNum(); + Quaternion q = SS.GetEntity(normal)->NormalGetNum(); + Vector u = q.RotationU(), v = q.RotationV(); + double r = CircleGetRadiusNum(); + double thetaa, thetab, dtheta; - Vector pm1 = BezierEval(tm1, p0, p1, p2, p3); - Vector pm2 = BezierEval(tm2, p0, p1, p2, p3); + if(type == CIRCLE) { + thetaa = 0; + thetab = 2*PI; + dtheta = 2*PI; + } else { + ArcGetAngles(&thetaa, &thetab, &dtheta); + } + int i, n; + if(dtheta > 3*PI/2) { + n = 4; + } else if(dtheta > PI) { + n = 3; + } else if(dtheta > PI/2) { + n = 2; + } else { + n = 1; + } + dtheta /= n; - double d = max(pm1.DistanceToLine(pa, pb.Minus(pa)), - pm2.DistanceToLine(pa, pb.Minus(pa))); + for(i = 0; i < n; i++) { + double s, c; - double tol = SS.chordTol/SS.GW.scale; + c = cos(thetaa); + s = sin(thetaa); + // The start point of the curve, and the tangent vector at + // that start point. + Vector p0 = center.Plus(u.ScaledBy( r*c)).Plus(v.ScaledBy(r*s)), + t0 = u.ScaledBy(-r*s). Plus(v.ScaledBy(r*c)); - double step = 1.0/SS.maxSegments; - if((tb - ta) < step || d < tol) { - LineDrawOrGetDistanceOrEdge(pa, pb); - } else { - double tm = (ta + tb) / 2; - BezierPwl(ta, tm, p0, p1, p2, p3); - BezierPwl(tm, tb, p0, p1, p2, p3); + thetaa += dtheta; + + c = cos(thetaa); + s = sin(thetaa); + Vector p2 = center.Plus(u.ScaledBy( r*c)).Plus(v.ScaledBy(r*s)), + t2 = u.ScaledBy(-r*s). Plus(v.ScaledBy(r*c)); + + // The control point must lie on both tangents. + Vector p1 = Vector::AtIntersectionOfLines(p0, p0.Plus(t0), + p2, p2.Plus(t2), + NULL); + + SPolyCurve spc = SPolyCurve::From(p0, p1, p2); + spc.weight[1] = cos(dtheta/2); + spcl->l.Add(&spc); + } + break; + } + + case TTF_TEXT: { + Vector topLeft = SS.GetEntity(point[0])->PointGetNum(); + Vector botLeft = SS.GetEntity(point[1])->PointGetNum(); + Vector n = Normal()->NormalN(); + Vector v = topLeft.Minus(botLeft); + Vector u = (v.Cross(n)).WithMagnitude(v.Magnitude()); + + SS.fonts.PlotString(font.str, str.str, 0, spcl, botLeft, u, v); + break; + } + + default: + // Not a problem, points and normals and such don't generate curves + break; } } - void Entity::DrawOrGetDistance(void) { - // If an entity is invisible, then it doesn't get shown, and it doesn't - // contribute a distance for the selection, but it still generates edges. - if(!dogd.edges) { - if(!IsVisible()) return; - } + if(!IsVisible()) return; Group *g = SS.GetGroup(group); @@ -350,77 +410,13 @@ void Entity::DrawOrGetDistance(void) { break; } - case LINE_SEGMENT: { - Vector a = SS.GetEntity(point[0])->PointGetNum(); - Vector b = SS.GetEntity(point[1])->PointGetNum(); - LineDrawOrGetDistanceOrEdge(a, b); + case LINE_SEGMENT: + case CIRCLE: + case ARC_OF_CIRCLE: + case CUBIC: + case TTF_TEXT: + // Nothing but the curve(s). break; - } - - case CUBIC: { - Vector p0 = SS.GetEntity(point[0])->PointGetNum(); - Vector p1 = SS.GetEntity(point[1])->PointGetNum(); - Vector p2 = SS.GetEntity(point[2])->PointGetNum(); - Vector p3 = SS.GetEntity(point[3])->PointGetNum(); - BezierPwl(0, 1, p0, p1, p2, p3); - break; - } - - case ARC_OF_CIRCLE: { - Vector c = SS.GetEntity(point[0])->PointGetNum(); - Vector pa = SS.GetEntity(point[1])->PointGetNum(); - Vector pb = SS.GetEntity(point[2])->PointGetNum(); - Quaternion q = SS.GetEntity(normal)->NormalGetNum(); - Vector u = q.RotationU(), v = q.RotationV(); - - double ra = (pa.Minus(c)).Magnitude(); - double rb = (pb.Minus(c)).Magnitude(); - - double thetaa, thetab, dtheta; - ArcGetAngles(&thetaa, &thetab, &dtheta); - - int i, n = 3 + (int)(SS.CircleSides(ra)*dtheta/(2*PI)); - Vector prev = pa; - for(i = 1; i <= n; i++) { - double theta = thetaa + (dtheta*i)/n; - double r = ra + ((rb - ra)*i)/n; - Vector d = u.ScaledBy(cos(theta)).Plus(v.ScaledBy(sin(theta))); - Vector p = c.Plus(d.ScaledBy(r)); - LineDrawOrGetDistanceOrEdge(prev, p); - prev = p; - } - break; - } - - case CIRCLE: { - Quaternion q = SS.GetEntity(normal)->NormalGetNum(); - double r = SS.GetEntity(distance)->DistanceGetNum(); - Vector center = SS.GetEntity(point[0])->PointGetNum(); - Vector u = q.RotationU(), v = q.RotationV(); - - int i, c = SS.CircleSides(r); - Vector prev = u.ScaledBy(r).Plus(center); - for(i = 1; i <= c; i++) { - double phi = (2*PI*i)/c; - Vector p = (u.ScaledBy(r*cos(phi))).Plus( - v.ScaledBy(r*sin(phi))); - p = p.Plus(center); - LineDrawOrGetDistanceOrEdge(prev, p); - prev = p; - } - break; - } - - case TTF_TEXT: { - Vector topLeft = SS.GetEntity(point[0])->PointGetNum(); - Vector botLeft = SS.GetEntity(point[1])->PointGetNum(); - Vector n = Normal()->NormalN(); - Vector v = topLeft.Minus(botLeft); - Vector u = (v.Cross(n)).WithMagnitude(v.Magnitude()); - - SS.fonts.PlotString(font.str, str.str, 0, h, botLeft, u, v); - break; - } case FACE_NORMAL_PT: case FACE_XPROD: @@ -433,5 +429,17 @@ void Entity::DrawOrGetDistance(void) { default: oops(); } + + // And draw the curves; generate the rational polynomial curves for + // everything, then piecewise linearize them, and display those. + SEdgeList sel; + ZERO(&sel); + GenerateEdges(&sel, true); + int i; + for(i = 0; i < sel.l.n; i++) { + SEdge *se = &(sel.l.elem[i]); + LineDrawOrGetDistance(se->a, se->b); + } + sel.Clear(); } diff --git a/sketch.h b/sketch.h index 1597e85..3349bab 100644 --- a/sketch.h +++ b/sketch.h @@ -393,21 +393,17 @@ public: bool drawing; Point2d mp; double dmin; - SEdgeList *edges; Vector refp; } dogd; // state for drawing or getting distance (for hit testing) void LineDrawOrGetDistance(Vector a, Vector b); - void LineDrawOrGetDistanceOrEdge(Vector a, Vector b); void DrawOrGetDistance(void); - void BezierPwl(double ta, double tb, - Vector p0, Vector p1, Vector p2, Vector p3); - Vector BezierEval(double t, Vector p0, Vector p1, Vector p2, Vector p3); + void GeneratePolyCurves(SPolyCurveList *spcl); + void GenerateEdges(SEdgeList *el, bool includingConstruction=false); static void DrawAll(void); void Draw(void); double GetDistance(Point2d mp); - void GenerateEdges(SEdgeList *el); Vector GetReferencePos(void); void AddEq(IdList *l, Expr *expr, int index); diff --git a/solvespace.h b/solvespace.h index c2e60ec..0048963 100644 --- a/solvespace.h +++ b/solvespace.h @@ -134,6 +134,7 @@ void vl(void); // debug function to validate heaps #include "dsc.h" #include "polygon.h" +#include "srf/surface.h" class Entity; class hEntity; @@ -291,8 +292,8 @@ public: // And the state that the caller must specify, determines where we // render to and how - hEntity entity; - Vector origin, u, v; + SPolyCurveList *polyCurves; + Vector origin, u, v; int Getc(void); int GetBYTE(void); @@ -307,7 +308,7 @@ public: void Handle(int *dx, int x, int y, bool onCurve); void PlotCharacter(int *dx, int c, double spacing); void PlotString(char *str, double spacing, - hEntity he, Vector origin, Vector u, Vector v); + SPolyCurveList *spcl, Vector origin, Vector u, Vector v); Vector TransformIntPoint(int x, int y); void LineSegment(int x0, int y0, int x1, int y1); @@ -324,7 +325,7 @@ public: void LoadAll(void); void PlotString(char *font, char *str, double spacing, - hEntity he, Vector origin, Vector u, Vector v); + SPolyCurveList *spcl, Vector origin, Vector u, Vector v); }; class VectorFileWriter { diff --git a/srf/ratpoly.cpp b/srf/ratpoly.cpp new file mode 100644 index 0000000..189d720 --- /dev/null +++ b/srf/ratpoly.cpp @@ -0,0 +1,129 @@ +#include "../solvespace.h" + +double Bernstein(int k, int deg, double t) +{ + switch(deg) { + case 1: + if(k == 0) { + return (1 - t); + } else if(k = 1) { + return t; + } + break; + + case 2: + if(k == 0) { + return (1 - t)*(1 - t); + } else if(k == 1) { + return 2*(1 - t)*t; + } else if(k == 2) { + return t*t; + } + break; + + case 3: + if(k == 0) { + return (1 - t)*(1 - t)*(1 - t); + } else if(k == 1) { + return 3*(1 - t)*(1 - t)*t; + } else if(k == 2) { + return 3*(1 - t)*t*t; + } else if(k == 3) { + return t*t*t; + } + break; + } + oops(); +} + +SPolyCurve SPolyCurve::From(Vector p0, Vector p1) { + SPolyCurve ret; + ZERO(&ret); + ret.deg = 1; + ret.weight[0] = ret.weight[1] = 1; + ret.ctrl[0] = p0; + ret.ctrl[1] = p1; + return ret; +} + +SPolyCurve SPolyCurve::From(Vector p0, Vector p1, Vector p2) { + SPolyCurve 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; +} + +SPolyCurve SPolyCurve::From(Vector p0, Vector p1, Vector p2, Vector p3) { + SPolyCurve 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 SPolyCurve::Start(void) { + return ctrl[0]; +} + +Vector SPolyCurve::Finish(void) { + return ctrl[deg]; +} + +Vector SPolyCurve::EvalAt(double t) { + Vector pt = Vector::From(0, 0, 0); + double d = 0; + + int i; + for(i = 0; i <= deg; i++) { + double B = Bernstein(i, deg, t); + pt = pt.Plus(ctrl[i].ScaledBy(B*weight[i])); + d += weight[i]*B; + } + pt = pt.ScaledBy(1.0/d); + return pt; +} + +void SPolyCurve::MakePwlInto(List *l) { + l->Add(&(ctrl[0])); + MakePwlWorker(l, 0.0, 1.0); +} + +void SPolyCurve::MakePwlWorker(List *l, double ta, double tb) { + Vector pa = EvalAt(ta); + Vector pb = EvalAt(tb); + + // Can't test in the middle, or certain cubics would break. + double tm1 = (2*ta + tb) / 3; + double tm2 = (ta + 2*tb) / 3; + + Vector pm1 = EvalAt(tm1); + Vector pm2 = EvalAt(tm2); + + double d = max(pm1.DistanceToLine(pa, pb.Minus(pa)), + pm2.DistanceToLine(pa, pb.Minus(pa))); + + double tol = SS.chordTol/SS.GW.scale; + + double step = 1.0/SS.maxSegments; + if((tb - ta) < step || d < tol) { + // A previous call has already added the beginning of our interval. + l->Add(&pb); + } else { + double tm = (ta + tb) / 2; + MakePwlWorker(l, ta, tm); + MakePwlWorker(l, tm, tb); + } +} + +void SPolyCurveList::Clear(void) { + l.Clear(); +} + diff --git a/srf/surface.h b/srf/surface.h index aee20e2..17582d3 100644 --- a/srf/surface.h +++ b/srf/surface.h @@ -2,8 +2,18 @@ #ifndef __SURFACE_H #define __SURFACE_H -class hSCurve; -class hSSurface; +// Utility function +double Bernstein(int k, int deg, double t); + +class hSSurface { +public: + DWORD v; +}; + +class hSCurve { +public: + DWORD v; +}; // Stuff for rational polynomial curves, of degree one to three. These are // our inputs. @@ -14,29 +24,30 @@ public: double weight[4]; Vector EvalAt(double t); - void GeneratePwlInto(SEdgeList *el); + Vector Start(void); + Vector Finish(void); + void MakePwlInto(List *l); + void MakePwlWorker(List *l, double ta, double tb); static SPolyCurve From(Vector p0, Vector p1, Vector p2, Vector p3); + static SPolyCurve From(Vector p0, Vector p1, Vector p2); static SPolyCurve From(Vector p0, Vector p1); }; class SPolyCurveList { public: List l; + + void Clear(void); }; // Stuff for the surface trim curves: piecewise linear -class hSCurve { -public: - DWORD v; -}; - class SCurve { public: hSCurve h; - SList pts; + List pts; hSSurface srfA; hSSurface srfB; }; @@ -53,11 +64,6 @@ public: Vector out; }; -class hSSurface { -public: - DWORD v; -}; - class SSurface { public: hSSurface h; @@ -66,7 +72,7 @@ public: Vector ctrl[4][4]; double weight[4][4]; - SList trim; + List trim; }; class SShell { diff --git a/ttf.cpp b/ttf.cpp index 08ec857..b2806cf 100644 --- a/ttf.cpp +++ b/ttf.cpp @@ -20,7 +20,8 @@ void TtfFontList::LoadAll(void) { } void TtfFontList::PlotString(char *font, char *str, double spacing, - hEntity he, Vector origin, Vector u, Vector v) + SPolyCurveList *spcl, + Vector origin, Vector u, Vector v) { LoadAll(); @@ -29,15 +30,17 @@ void TtfFontList::PlotString(char *font, char *str, double spacing, TtfFont *tf = &(l.elem[i]); if(strcmp(tf->FontFileBaseName(), font)==0) { tf->LoadFontFromFile(false); - tf->PlotString(str, spacing, he, origin, u, v); + tf->PlotString(str, spacing, spcl, origin, u, v); return; } } // Couldn't find the font; so draw a big X for an error marker. - Entity *e = SS.GetEntity(he); - e->LineDrawOrGetDistanceOrEdge(origin, origin.Plus(u).Plus(v)); - e->LineDrawOrGetDistanceOrEdge(origin.Plus(v), origin.Plus(u)); + SPolyCurve spc; + spc = SPolyCurve::From(origin, origin.Plus(u).Plus(v)); + spcl->l.Add(&spc); + spc = SPolyCurve::From(origin.Plus(v), origin.Plus(u)); + spcl->l.Add(&spc); } @@ -654,9 +657,10 @@ void TtfFont::PlotCharacter(int *dx, int c, double spacing) { } void TtfFont::PlotString(char *str, double spacing, - hEntity he, Vector porigin, Vector pu, Vector pv) + SPolyCurveList *spcl, + Vector porigin, Vector pu, Vector pv) { - entity = he; + polyCurves = spcl; u = pu; v = pv; origin = porigin; @@ -685,42 +689,15 @@ Vector TtfFont::TransformIntPoint(int x, int y) { } void TtfFont::LineSegment(int x0, int y0, int x1, int y1) { - Entity *e = SS.GetEntity(entity); - e->LineDrawOrGetDistanceOrEdge(TransformIntPoint(x0, y0), - TransformIntPoint(x1, y1)); -} - -Vector TtfFont::BezierEval(double t, Vector p0, Vector p1, Vector p2) { - return (p0.ScaledBy((1 - t)*(1 - t))).Plus( - (p1.ScaledBy(2*t*(1 - t))).Plus( - (p2.ScaledBy(t*t)))); -} - -void TtfFont::BezierPwl(double ta, double tb, Vector p0, Vector p1, Vector p2) { - Vector pa = BezierEval(ta, p0, p1, p2); - Vector pb = BezierEval(tb, p0, p1, p2); - - double tm = (ta + tb) / 2; - - Vector pm = BezierEval(tm, p0, p1, p2); - - double tol = SS.chordTol/SS.GW.scale; - - double step = 1.0/SS.maxSegments; - if((tb - ta) < step || pm.DistanceToLine(pa, pb.Minus(pa)) < tol) { - Entity *e = SS.GetEntity(entity); - e->LineDrawOrGetDistanceOrEdge(pa, pb); - } else { - BezierPwl(ta, tm, p0, p1, p2); - BezierPwl(tm, tb, p0, p1, p2); - } + SPolyCurve spc = SPolyCurve::From(TransformIntPoint(x0, y0), + TransformIntPoint(x1, y1)); + polyCurves->l.Add(&spc); } void TtfFont::Bezier(int x0, int y0, int x1, int y1, int x2, int y2) { - Vector p0 = TransformIntPoint(x0, y0), - p1 = TransformIntPoint(x1, y1), - p2 = TransformIntPoint(x2, y2); - - BezierPwl(0, 1, p0, p1, p2); + SPolyCurve spc = SPolyCurve::From(TransformIntPoint(x0, y0), + TransformIntPoint(x1, y1), + TransformIntPoint(x2, y2)); + polyCurves->l.Add(&spc); }