Entities now generate rational polynomial curves instead of

piecwise linear segments. These are piecewise linear approximated
for display, and currently for the mesh too, but that's the first
step to replace the mesh with exact curved surfaces.

[git-p4: depot-paths = "//depot/solvespace/": change = 1895]
solver
Jonathan Westhues 2009-01-14 19:55:42 -08:00
parent b8da4ed2b3
commit f904c0fbee
7 changed files with 309 additions and 186 deletions

View File

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

View File

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

View File

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

View File

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

129
srf/ratpoly.cpp Normal file
View File

@ -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<Vector> *l) {
l->Add(&(ctrl[0]));
MakePwlWorker(l, 0.0, 1.0);
}
void SPolyCurve::MakePwlWorker(List<Vector> *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();
}

View File

@ -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<Vector> *l);
void MakePwlWorker(List<Vector> *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<SPolyCurve> l;
void Clear(void);
};
// Stuff for the surface trim curves: piecewise linear
class hSCurve {
public:
DWORD v;
};
class SCurve {
public:
hSCurve h;
SList<Vector> pts;
List<Vector> 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<STrimBy> trim;
List<STrimBy> trim;
};
class SShell {

59
ttf.cpp
View File

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