1
//-----------------------------------------------------------------------------
2
// Copyright 2008 Jonathan Westhues
4
// This file is part of SketchFlat.
6
// SketchFlat is free software: you can redistribute it and/or modify
7
// it under the terms of the GNU General Public License as published by
8
// the Free Software Foundation, either version 3 of the License, or
9
// (at your option) any later version.
11
// SketchFlat is distributed in the hope that it will be useful,
12
// but WITHOUT ANY WARRANTY; without even the implied warranty of
13
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
// GNU General Public License for more details.
16
// You should have received a copy of the GNU General Public License
17
// along with SketchFlat. If not, see <http://www.gnu.org/licenses/>.
20
// Routines to work with our parametric curves. When we get the curves, they
21
// already have constant numerical coefficients.
22
// Jonathan Westhues, April 2007
23
//-----------------------------------------------------------------------------
24
#include "PreCompiled.h"
28
#include "sketchflat.h"
30
#define CHORD_TOLERANCE_IN_PIXELS 0.25
32
static DoublePoint TransX, TransY, TransOffset;
34
static DoublePoint ImportMin, ImportMax;
36
void CurveEval(SketchCurve *c, double t, double *xp, double *yp);
38
static void AddPwl(hEntity id, hLayer layer, BOOL construction,
39
double x0, double y0, double x1, double y1);
41
static void Zero(SketchCurve *c)
62
static void AddCurve(SketchCurve *c)
64
SketchEntity *e = EntityById(c->id);
65
// The curve is on whatever layer the entity that generates it is on.
67
// and has the same construction flag.
68
c->construction = e->construction;
71
if(i < (MAX_CURVES_IN_SKETCH-1)) {
72
memcpy(&(SK->curve[i]), c, sizeof(*c));
77
static void AddBezierCubic(hEntity he, double *x, double *y)
79
// The cubic has the form
80
// P[0]*(1-t)^3 + 3*P[1]*t*(1-t)^2 + 3*P[2]*t^2*(1-t) + P[3]*t^3
82
// (-P[0]+3*P[1]-3*P[2]+P[3]) * t^3 +
83
// (3*P[0]+3*P[2]-6*P[1]) * t^2 +
84
// (-3*P[0]+3*P[1]) * t +
91
c.x.A = -x[0] + 3*x[1] - 3*x[2] + x[3];
92
c.y.A = -y[0] + 3*y[1] - 3*y[2] + y[3];
94
c.x.B = 3*x[0] - 6*x[1] + 3*x[2];
95
c.y.B = 3*y[0] - 6*y[1] + 3*y[2];
97
c.x.C = -3*x[0] + 3*x[1];
98
c.y.C = -3*y[0] + 3*y[1];
106
static void FromTtfTransform(int xi, int yi, double *xo, double *yo)
113
// output = xf*TransX + yf*TransY
114
*xo = xf*(TransX.x) + yf*(TransY.x) + TransOffset.x;
115
*yo = xf*(TransX.y) + yf*(TransY.y) + TransOffset.y;
118
static void FromImportedTransform(double *x, double *y)
120
double xi = *x, yi = *y;
125
double scale = (ImportMax.y - ImportMin.y);
126
if(scale == 0) scale = 1;
130
*x = (xi*TransX.x) + (yi*TransY.x) + TransOffset.x;
131
*y = (xi*TransX.y) + (yi*TransY.y) + TransOffset.y;
134
void TtfLineSegment(DWORD ref, int x0, int y0, int x1, int y1)
136
double x0f, y0f, x1f, y1f;
138
FromTtfTransform(x0, y0, &x0f, &y0f);
139
FromTtfTransform(x1, y1, &x1f, &y1f);
154
void TtfBezier(DWORD ref, int x0, int y0, int x1, int y1, int x2, int y2)
156
double x0f, y0f, x1f, y1f, x2f, y2f;
158
FromTtfTransform(x0, y0, &x0f, &y0f);
159
FromTtfTransform(x1, y1, &x1f, &y1f);
160
FromTtfTransform(x2, y2, &x2f, &y2f);
165
// The Bezier curve has the form:
167
// P(t) = P0*(1 - t)^2 + P1*2*t*(1 - t) + P2*t^2
168
// = P0 - P0*2*t + P0*t^2 + P1*2*t - P1*2*t^2 + P2*t^2
169
// = P0 + t*(-P0*2 + P1*2) + (t^2)*(P0 - P1*2 + P2)
171
// Of course it would be better to evaluate with de Casteljau's or
172
// whatever, but this is more general.
176
c.x.C = (-x0f*2 + x1f*2);
177
c.y.C = (-y0f*2 + y1f*2);
178
c.x.B = (x0f - x1f*2 + x2f);
179
c.y.B = (y0f - y1f*2 + y2f);
186
//-----------------------------------------------------------------------------
187
// For the HPGL/DXF import: we want to scale the artwork as we import it,
188
// to put the two reference points at the corners of its bounding box. So
189
// we need to calculate the bounding box of the file, which we do on our
190
// first pass through.
191
//-----------------------------------------------------------------------------
192
static void RecordBounds(double x0, double y0, double x1, double y1)
195
for(i = 0; i < 2; i++) {
203
if(x > ImportMax.x) ImportMax.x = x;
204
if(x < ImportMin.x) ImportMin.x = x;
205
if(y > ImportMax.y) ImportMax.y = y;
206
if(y < ImportMin.y) ImportMin.y = y;
210
//-----------------------------------------------------------------------------
211
// Import an HPGL file. If justGetBound is TRUE, then we don't generate pwls,
212
// but call RecordBounds() to measure the bounding box.
213
//-----------------------------------------------------------------------------
214
static BOOL ImportFromHpgl(hEntity he, hLayer hl, char *file, BOOL justGetBound)
216
FILE *f = fopen(file, "r");
219
double prevX = 0, prevY = 0;
221
#define GET_CHAR_INTO(c) (c) = fgetc(f); if((c) < 0) goto done
225
// First, look for a command.
229
if(prev == 'p' && (now == 'd' || now == 'u')) break;
232
// Is it followed by a number?
238
if(isdigit(c) || c == '.' || c == '-') {
239
if(xbufp > 10) break;
245
// Burn extra separators
249
if(c == ',' || c == ' ') {
256
// And then get the y
262
if(isdigit(c) || c == '.' || c == '-') {
263
if(ybufp > 10) break;
277
RecordBounds(prevX, prevY, x, y);
279
FromImportedTransform(&x, &y);
281
AddPwl(he, hl, FALSE, prevX, prevY, x, y);
293
//-----------------------------------------------------------------------------
294
// Import a DXF file. If justGetBound is TRUE, then we don't generate pwls,
295
// but call RecordBounds() to measure the bounding box.
296
//-----------------------------------------------------------------------------
297
static BOOL ImportFromDxf(hEntity he, hLayer hl, char *file, BOOL justGetBound)
299
FILE *f = fopen(file, "r");
302
char line[MAX_STRING];
304
#define GET_LINE_INTO(s) if(!fgets(s, sizeof(s), f)) goto done
309
while(isspace(*s)) s++;
310
while(isspace(s[strlen(s)-1])) s[strlen(s)-1] = '\0';
312
if(strcmp(s, "LINE")==0) {
313
char x0[MAX_STRING] = "", y0[MAX_STRING] = "";
314
char x1[MAX_STRING] = "", y1[MAX_STRING] = "";
320
case 10: GET_LINE_INTO(x0); have |= 1; break;
321
case 20: GET_LINE_INTO(y0); have |= 2; break;
322
case 11: GET_LINE_INTO(x1); have |= 4; break;
323
case 21: GET_LINE_INTO(y1); have |= 8; break;
327
default: GET_LINE_INTO(line); break;
330
// Do we have all four paramter values?
331
if(have == 0xf) break;
334
double x0f, y0f, x1f, y1f;
341
RecordBounds(x0f, y0f, x1f, y1f);
343
FromImportedTransform(&x0f, &y0f);
344
FromImportedTransform(&x1f, &y1f);
345
AddPwl(he, hl, FALSE, x0f, y0f, x1f, y1f);
356
//-----------------------------------------------------------------------------
357
// Import some type of file, determining which according to its extension. If
358
// the file import fails, then draw an X, as an indication to the user that
359
// something broke. Also record the extent of the imported file, to display
360
// in the measurement view, since the user might want to use that to scale
362
//-----------------------------------------------------------------------------
363
static BOOL ImportFromFile(hEntity he, hLayer hl, char *file)
365
char *ext = file + strlen(file) - 4;
367
ImportMax.x = VERY_NEGATIVE;
368
ImportMax.y = VERY_NEGATIVE;
369
ImportMin.x = VERY_POSITIVE;
370
ImportMin.y = VERY_POSITIVE;
372
int SKpwls0 = SK->pwls;
374
// Guess the file type from the extension.
375
if(_stricmp(ext, ".plt")==0 || _stricmp(ext, "hpgl")==0) {
376
ImportFromHpgl(he, hl, file, TRUE);
377
ImportFromHpgl(he, hl, file, FALSE);
378
} else if(_stricmp(ext, ".dxf")==0) {
379
ImportFromDxf(he, hl, file, TRUE);
380
ImportFromDxf(he, hl, file, FALSE);
383
// If we didn't generate any piecewise linear segments, then probably
384
// something broke. Show an X in construction line segments, so that the
385
// user still has something to grab and select.
386
if(SKpwls0 == SK->pwls) {
387
double x0, y0, x1, y1;
388
ImportMax.x = 1; ImportMax.y = 1;
389
ImportMin.x = 0; ImportMin.y = 0;
390
x0 = 0; y0 = 0; x1 = 1; y1 = 1;
391
FromImportedTransform(&x0, &y0);
392
FromImportedTransform(&x1, &y1);
393
AddPwl(he, hl, TRUE, x0, y0, x1, y1);
394
x0 = 1; y0 = 0; x1 = 0; y1 = 1;
395
FromImportedTransform(&x0, &y0);
396
FromImportedTransform(&x1, &y1);
397
AddPwl(he, hl, TRUE, x0, y0, x1, y1);
400
// Record the original bounding box of the imported file; the measure
401
// stuff will display it from here.
402
SketchEntity *e = EntityById(he);
404
sprintf(e->text, " (%.3f, %.3f)\r\n"
407
ImportMin.x, ImportMin.y,
408
ImportMax.x, ImportMax.y,
409
ImportMax.y - ImportMin.y);
415
void GenerateCurvesFromEntity(SketchEntity *e)
423
case ENTITY_DATUM_POINT:
424
// No curves associated with this entity.
427
case ENTITY_DATUM_LINE:
428
// No curves associated with this entity (infinitely long lines
429
// are a special case, distinct from line segments).
432
case ENTITY_LINE_SEGMENT:
433
pt0 = POINT_FOR_ENTITY(e->id, 0);
434
pt1 = POINT_FOR_ENTITY(e->id, 1);
437
c.x.D = EvalParam(X_COORD_FOR_PT(pt0));
438
c.y.D = EvalParam(Y_COORD_FOR_PT(pt0));
439
c.x.C = EvalParam(X_COORD_FOR_PT(pt1)) - c.x.D;
440
c.y.C = EvalParam(Y_COORD_FOR_PT(pt1)) - c.y.D;
447
pt0 = POINT_FOR_ENTITY(e->id, 0);
448
prm0 = PARAM_FOR_ENTITY(e->id, 0);
451
EvalPoint(pt0, &(c.x.D), &(c.y.D));
452
c.x.R = c.y.R = EvalParam(prm0);
461
case ENTITY_CIRCULAR_ARC: {
462
double x0, y0, x1, y1;
463
EvalPoint(POINT_FOR_ENTITY(e->id, 0), &x0, &y0);
464
EvalPoint(POINT_FOR_ENTITY(e->id, 1), &x1, &y1);
466
EvalPoint(POINT_FOR_ENTITY(e->id, 2), &xc, &yc);
468
double r0 = Distance(xc, yc, x0, y0);
469
double r1 = Distance(xc, yc, x1, y1);
471
double phi0 = atan2(y0 - yc, x0 - xc);
472
double phi1 = atan2(y1 - yc, x1 - xc);
474
double dphi = phi0 - phi1;
475
while(dphi < 0) dphi += 2*PI;
476
while(dphi >= 2*PI) dphi -= 2*PI;
486
c.y.phi = PI/2 + phi0;
494
case ENTITY_CUBIC_SPLINE: {
496
double xnp, ynp; // previous oN curve point
497
double xfp, yfp; // previous oFf curve point
502
int max = (e->points - 2) / 2;
503
for(i = 0; i < max; i++) {
505
EvalPoint(POINT_FOR_ENTITY(e->id, pt), &(x[0]), &(y[0]));
507
EvalPoint(POINT_FOR_ENTITY(e->id, pt), &(x[1]), &(y[1]));
517
EvalPoint(POINT_FOR_ENTITY(e->id, pt), &(x[2]), &(y[2]));
519
EvalPoint(POINT_FOR_ENTITY(e->id, pt), &(x[3]), &(y[3]));
522
EvalPoint(POINT_FOR_ENTITY(e->id, pt), &(x[2]), &(y[2]));
524
EvalPoint(POINT_FOR_ENTITY(e->id, pt), &xfp, &yfp);
526
xnp = x[3] = (xfp + x[2]) / 2;
527
ynp = y[3] = (yfp + y[2]) / 2;
530
AddBezierCubic(e->id, x, y);
536
case ENTITY_TTF_TEXT:
537
case ENTITY_IMPORTED: {
538
double x0, y0, x1, y1;
539
EvalPoint(POINT_FOR_ENTITY(e->id, 0), &x0, &y0);
540
EvalPoint(POINT_FOR_ENTITY(e->id, 1), &x1, &y1);
546
TransX.y = -TransY.x;
551
if(e->type == ENTITY_TTF_TEXT) {
552
TtfSelectFont(e->file);
553
TtfPlotString(e->id, e->text, e->spacing);
555
ImportFromFile(e->id, e->layer, e->file);
565
void GenerateCurves(void)
570
for(i = 0; i < SK->entities; i++) {
571
GenerateCurvesFromEntity(&(SK->entity[i]));
575
void CurveEval(SketchCurve *c, double t, double *xp, double *yp)
583
x += (c->x.R + (c->x.Rl)*t) * cos((c->omega)*t + c->x.phi);
589
y += (c->y.R + (c->y.Rl)*t) * cos((c->omega)*t + c->y.phi);
595
static void AddPwl(hEntity id, hLayer layer, BOOL construction,
596
double x0, double y0, double x1, double y1)
600
if(i >= (MAX_PWLS_IN_SKETCH-1)) return;
602
SketchPwl *p = &(SK->pwl[i]);
606
p->construction = construction;
616
//-----------------------------------------------------------------------------
617
// Break a curve down in to its piecewise linear representation. The number
618
// of points is determined by a "chord tolerance". We initially try to
619
// generate a single line for the entire curve, but halve the remaining
620
// interval each time we fail.
621
//-----------------------------------------------------------------------------
622
static void GeneratePwlsFromCurve(SketchCurve *c, double chordTol)
630
double tryTo = finalTo;
632
int pwls0 = SK->pwls;
634
while(from < (finalTo - 0.001)) {
635
double xi, yi; // Starting point of the line we are considering
636
double xf, yf; // Ending point of the line we are considering
637
double xm, ym; // A point on the curve midway between start, end
638
double xml, yml; // The midpoint of the line we are considering
640
if(c->x.A != 0 || c->y.A != 0) {
641
// A cubic might pass through the midpoint of the line connecting
642
// its endpoints, but deviate from that line elsewhere.
643
if(tryTo - from > 0.1) {
644
tryTo = std::min<double>(finalTo, from + 0.1);
648
CurveEval(c, from, &xi, &yi);
649
CurveEval(c, tryTo, &xf, &yf);
650
CurveEval(c, (from + tryTo)/2, &xm, &ym);
652
dbp("from (%.3f, %.3f) at %.3f to (%.3f, %.3f) at %.3f",
659
if(Distance(xm, ym, xml, yml) < chordTol) {
661
AddPwl(c->id, c->layer, c->construction, xi, yi, xf, yf);
666
tryTo = from + (tryTo - from)/2;
671
if(pts > 200 || iters > 1000) {
672
// If we get too many points trying to plot the thing cleverly
673
// and adaptively, then give up and just generate 200 evenly
677
CurveEval(c, 0, &xi, &yi);
679
double dt = 1.0/steps;
680
for(t = dt; t < 1 + dt; t += dt) {
681
CurveEval(c, t, &xf, &yf);
682
AddPwl(c->id, c->layer, c->construction, xi, yi, xf, yf);
690
void GenerateCurvesAndPwls(double chordTol)
694
// First, create the various curves.
697
// Then break them down to piecewise linear segments. The chord
698
// tolerance with which we do this is caller-configurable.
701
// They want our default display tolerance.
703
toMicronsNotAffine((int)(CHORD_TOLERANCE_IN_PIXELS*100))/100.0;
706
// And adaptive-pwl each curve.
708
for(i = 0; i < SK->curves; i++) {
709
GeneratePwlsFromCurve(&(SK->curve[i]), chordTol);