]> Shamusworld >> Repos - architektonas/blob - src/base/ellipse.cpp
Bugfixes related to removing Snapper class.
[architektonas] / src / base / ellipse.cpp
1 // ellipse.cpp
2 //
3 // Part of the Architektonas Project
4 // Originally part of QCad Community Edition by Andrew Mustun
5 // Extensively rewritten and refactored by James L. Hammons
6 // Portions copyright (C) 2001-2003 RibbonSoft
7 // Copyright (C) 2010 Underground Software
8 // See the README and GPLv2 files for licensing and warranty information
9 //
10 // JLH = James L. Hammons <jlhamm@acm.org>
11 //
12 // Who  When        What
13 // ---  ----------  -----------------------------------------------------------
14 // JLH  05/28/2010  Added this text. :-)
15 //
16
17 #include "ellipse.h"
18
19 #include "drawing.h"
20 #include "graphicview.h"
21 #include "information.h"
22 #include "linetypepattern.h"
23 #include "paintinterface.h"
24
25 /**
26  * Constructor.
27  */
28 Ellipse::Ellipse(EntityContainer * parent, const EllipseData & d):
29         AtomicEntity(parent), data(d)
30 {
31         //calculateEndpoints();
32         calculateBorders();
33 }
34
35 /*virtual*/ Ellipse::~Ellipse()
36 {
37 }
38
39 /*virtual*/ Entity * Ellipse::clone()
40 {
41         Ellipse * e = new Ellipse(*this);
42         e->initId();
43         return e;
44 }
45
46 /**     @return RS2::EntityEllipse */
47 /*virtual*/ RS2::EntityType Ellipse::rtti() const
48 {
49         return RS2::EntityEllipse;
50 }
51
52 /**
53  * @return Start point of the entity.
54  */
55 /*virtual*/ Vector Ellipse::getStartpoint() const
56 {
57         Vector p;
58         p.set(data.center.x + cos(data.angle1) * getMajorRadius(),
59                 data.center.y + sin(data.angle1) * getMinorRadius());
60         p.rotate(data.center, getAngle());
61         return p;
62 }
63
64 /**
65  * @return End point of the entity.
66  */
67 /*virtual*/ Vector Ellipse::getEndpoint() const
68 {
69         Vector p;
70         p.set(data.center.x + cos(data.angle2) * getMajorRadius(),
71                 data.center.y + sin(data.angle2) * getMinorRadius());
72         p.rotate(data.center, getAngle());
73         return p;
74 }
75
76 void Ellipse::moveStartpoint(const Vector & pos)
77 {
78         data.angle1 = getEllipseAngle(pos);
79         //data.angle1 = data.center.angleTo(pos);
80         //calculateEndpoints();
81         calculateBorders();
82 }
83
84 void Ellipse::moveEndpoint(const Vector & pos)
85 {
86         data.angle2 = getEllipseAngle(pos);
87         //data.angle2 = data.center.angleTo(pos);
88         //calculateEndpoints();
89         calculateBorders();
90 }
91
92 RS2::Ending Ellipse::getTrimPoint(const Vector & coord, const Vector & trimPoint)
93 {
94         double angEl = getEllipseAngle(trimPoint);
95         double angM = getEllipseAngle(coord);
96
97         if (Math::getAngleDifference(angM, angEl) > M_PI)
98                 //if (data.reversed) {
99                 //      return RS2::EndingEnd;
100                 //}
101                 //else {
102                 return RS2::EndingStart;
103                 //}
104         else
105                 //if (data.reversed) {
106                 //      return RS2::EndingStart;
107                 //}
108                 //else {
109                 return RS2::EndingEnd;
110                 //}
111 }
112
113 double Ellipse::getEllipseAngle(const Vector & pos)
114 {
115         Vector m = pos;
116         m.rotate(data.center, -data.majorP.angle());
117         Vector v = m - data.center;
118         v.scale(Vector(1.0, 1.0 / data.ratio));
119         return v.angle();
120 }
121
122 /** @return Copy of data that defines the ellipse. **/
123 EllipseData Ellipse::getData()
124 {
125         return data;
126 }
127
128 VectorSolutions Ellipse::getRefPoints()
129 {
130         VectorSolutions ret(getStartpoint(), getEndpoint(), data.center);
131         return ret;
132 }
133
134 /**
135  * @retval true if the arc is reversed (clockwise),
136  * @retval false otherwise
137  */
138 bool Ellipse::isReversed() const
139 {
140         return data.reversed;
141 }
142
143 /** sets the reversed status. */
144 void Ellipse::setReversed(bool r)
145 {
146         data.reversed = r;
147 }
148
149 /** @return The rotation angle of this ellipse */
150 double Ellipse::getAngle() const
151 {
152         return data.majorP.angle();
153 }
154
155 /** @return The start angle of this arc */
156 double Ellipse::getAngle1()
157 {
158         return data.angle1;
159 }
160
161 /** Sets new start angle. */
162 void Ellipse::setAngle1(double a1)
163 {
164         data.angle1 = a1;
165 }
166
167 /** @return The end angle of this arc */
168 double Ellipse::getAngle2()
169 {
170         return data.angle2;
171 }
172
173 /** Sets new end angle. */
174 void Ellipse::setAngle2(double a2)
175 {
176         data.angle2 = a2;
177 }
178
179 /** @return The center point (x) of this arc */
180 Vector Ellipse::getCenter()
181 {
182         return data.center;
183 }
184
185 /** Sets new center. */
186 void Ellipse::setCenter(const Vector & c)
187 {
188         data.center = c;
189 }
190
191 /** @return The endpoint of the major axis (relative to center). */
192 Vector Ellipse::getMajorP()
193 {
194         return data.majorP;
195 }
196
197 /** Sets new major point (relative to center). */
198 void Ellipse::setMajorP(const Vector & p)
199 {
200         data.majorP = p;
201 }
202
203 /** @return The ratio of minor to major axis */
204 double Ellipse::getRatio()
205 {
206         return data.ratio;
207 }
208
209 /** Sets new ratio. */
210 void Ellipse::setRatio(double r)
211 {
212         data.ratio = r;
213 }
214
215 /**
216  * @return Angle length in rad.
217  */
218 /*virtual*/ double Ellipse::getAngleLength() const
219 {
220         if (isReversed())
221                 return data.angle1 - data.angle2;
222         else
223                 return data.angle2 - data.angle1;
224 }
225
226 /** @return The major radius of this ellipse. Same as getRadius() */
227 double Ellipse::getMajorRadius() const
228 {
229         return data.majorP.magnitude();
230 }
231
232 /** @return The minor radius of this ellipse */
233 double Ellipse::getMinorRadius() const
234 {
235         return data.majorP.magnitude() * data.ratio;
236 }
237
238 /**
239  * Recalculates the endpoints using the angles and the radius.
240  */
241 /*
242    void Ellipse::calculateEndpoints() {
243    double angle = data.majorP.angle();
244    double radius1 = getMajorRadius();
245    double radius2 = getMinorRadius();
246
247    startpoint.set(data.center.x + cos(data.angle1) * radius1,
248                   data.center.y + sin(data.angle1) * radius2);
249    startpoint.rotate(data.center, angle);
250    endpoint.set(data.center.x + cos(data.angle2) * radius1,
251                 data.center.y + sin(data.angle2) * radius2);
252    endpoint.rotate(data.center, angle);
253    }
254  */
255
256 /**
257  * Calculates the boundary box of this ellipse.
258  *
259  * @todo Fix that - the algorithm used is really bad / slow.
260  */
261 void Ellipse::calculateBorders()
262 {
263         DEBUG->print("Ellipse::calculateBorders");
264
265         double radius1 = getMajorRadius();
266         double radius2 = getMinorRadius();
267         double angle = getAngle();
268         double a1 = ((!isReversed()) ? data.angle1 : data.angle2);
269         double a2 = ((!isReversed()) ? data.angle2 : data.angle1);
270         Vector startpoint = getStartpoint();
271         Vector endpoint = getEndpoint();
272
273         double minX = std::min(startpoint.x, endpoint.x);
274         double minY = std::min(startpoint.y, endpoint.y);
275         double maxX = std::max(startpoint.x, endpoint.x);
276         double maxY = std::max(startpoint.y, endpoint.y);
277
278         // kind of a brute force. TODO: exact calculation
279         Vector vp;
280         double a = a1;
281
282         do
283         {
284                 vp.set(data.center.x + radius1 * cos(a),
285                         data.center.y + radius2 * sin(a));
286                 vp.rotate(data.center, angle);
287
288                 minX = std::min(minX, vp.x);
289                 minY = std::min(minY, vp.y);
290                 maxX = std::max(maxX, vp.x);
291                 maxY = std::max(maxY, vp.y);
292
293                 a += 0.03;
294         }
295         while (Math::isAngleBetween(Math::correctAngle(a), a1, a2, false)
296                && a < 4 * M_PI);
297
298         minV.set(minX, minY);
299         maxV.set(maxX, maxY);
300         DEBUG->print("Ellipse::calculateBorders: OK");
301 }
302
303 Vector Ellipse::getNearestEndpoint(const Vector & coord, double * dist)
304 {
305         double dist1, dist2;
306         Vector nearerPoint;
307         Vector startpoint = getStartpoint();
308         Vector endpoint = getEndpoint();
309
310         dist1 = startpoint.distanceTo(coord);
311         dist2 = endpoint.distanceTo(coord);
312
313         if (dist2 < dist1)
314         {
315                 if (dist != NULL)
316                         *dist = dist2;
317                 nearerPoint = endpoint;
318         }
319         else
320         {
321                 if (dist != NULL)
322                         *dist = dist1;
323                 nearerPoint = startpoint;
324         }
325
326         return nearerPoint;
327 }
328
329 Vector Ellipse::getNearestPointOnEntity(const Vector & coord, bool onEntity, double * dist, Entity * * entity)
330 {
331         DEBUG->print("Ellipse::getNearestPointOnEntity");
332
333         Vector ret(false);
334
335         if (entity != NULL)
336                 *entity = this;
337         double ang = getAngle();
338
339         Vector normalized = (coord - data.center).rotate(-ang);
340
341         double dU = normalized.x;
342         double dV = normalized.y;
343         double dA = getMajorRadius();
344         double dB = getMinorRadius();
345         double dEpsilon = 1.0e-8;
346         int iMax = 32;
347         int riIFinal = 0;
348         double rdX = 0.0;
349         double rdY = 0.0;
350         double dDistance;
351         bool swap = false;
352         bool majorSwap = false;
353
354         if (dA < dB)
355         {
356                 double dum = dA;
357                 dA = dB;
358                 dB = dum;
359                 dum = dU;
360                 dU = dV;
361                 dV = dum;
362                 majorSwap = true;
363         }
364
365         if (dV < 0.0)
366         {
367                 dV *= -1.0;
368                 swap = true;
369         }
370
371         // initial guess
372         double dT = dB * (dV - dB);
373
374         // Newton s method
375         int i;
376
377         for (i = 0; i < iMax; i++)
378         {
379                 DEBUG->print("Ellipse::getNearestPointOnEntity: i: %d", i);
380                 double dTpASqr = dT + dA * dA;
381                 double dTpBSqr = dT + dB * dB;
382                 double dInvTpASqr = 1.0 / dTpASqr;
383                 double dInvTpBSqr = 1.0 / dTpBSqr;
384                 double dXDivA = dA * dU * dInvTpASqr;
385                 double dYDivB = dB * dV * dInvTpBSqr;
386                 double dXDivASqr = dXDivA * dXDivA;
387                 double dYDivBSqr = dYDivB * dYDivB;
388                 double dF = dXDivASqr + dYDivBSqr - 1.0;
389                 DEBUG->print("Ellipse::getNearestPointOnEntity: dF: %f", dF);
390
391                 if (fabs(dF) < dEpsilon)
392                 {
393                         // F(t0) is close enough to zero, terminate the iteration:
394                         rdX = dXDivA * dA;
395                         rdY = dYDivB * dB;
396                         riIFinal = i;
397                         DEBUG->print("Ellipse::getNearestPointOnEntity: rdX,rdY 1: %f,%f", rdX, rdY);
398                         break;
399                 }
400                 double dFDer = 2.0 * (dXDivASqr * dInvTpASqr + dYDivBSqr * dInvTpBSqr);
401                 double dRatio = dF / dFDer;
402                 DEBUG->print("Ellipse::getNearestPointOnEntity: dRatio: %f", dRatio);
403
404                 if (fabs(dRatio) < dEpsilon)
405                 {
406                         // t1-t0 is close enough to zero, terminate the iteration:
407                         rdX = dXDivA * dA;
408                         rdY = dYDivB * dB;
409                         riIFinal = i;
410                         DEBUG->print("Ellipse::getNearestPointOnEntity: rdX,rdY 2: %f,%f", rdX, rdY);
411                         break;
412                 }
413                 dT += dRatio;
414         }
415
416         if (i == iMax)
417         {
418                 // failed to converge:
419                 DEBUG->print("Ellipse::getNearestPointOnEntity: failed");
420                 dDistance = RS_MAXDOUBLE;
421         }
422         else
423         {
424                 double dDelta0 = rdX - dU;
425                 double dDelta1 = rdY - dV;
426                 dDistance = sqrt(dDelta0 * dDelta0 + dDelta1 * dDelta1);
427                 ret = Vector(rdX, rdY);
428                 DEBUG->print("Ellipse::getNearestPointOnEntity: rdX,rdY 2: %f,%f", rdX, rdY);
429                 DEBUG->print("Ellipse::getNearestPointOnEntity: ret: %f,%f", ret.x, ret.y);
430         }
431
432         if (dist != NULL)
433         {
434                 if (ret.valid)
435                         *dist = dDistance;
436                 else
437                         *dist = RS_MAXDOUBLE;
438         }
439
440         if (ret.valid)
441         {
442                 if (swap)
443                         ret.y *= -1.0;
444
445
446                 if (majorSwap)
447                 {
448                         double dum = ret.x;
449                         ret.x = ret.y;
450                         ret.y = dum;
451                 }
452                 ret = (ret.rotate(ang) + data.center);
453
454                 if (onEntity)
455                 {
456                         double a1 = data.center.angleTo(getStartpoint());
457                         double a2 = data.center.angleTo(getEndpoint());
458                         double a = data.center.angleTo(ret);
459
460                         if (!Math::isAngleBetween(a, a1, a2, data.reversed))
461                                 ret = Vector(false);
462                 }
463         }
464
465         return ret;
466 }
467
468 /**
469  * @param tolerance Tolerance.
470  *
471  * @retval true if the given point is on this entity.
472  * @retval false otherwise
473  */
474 bool Ellipse::isPointOnEntity(const Vector & coord, double tolerance)
475 {
476         double dist = getDistanceToPoint(coord, NULL, RS2::ResolveNone);
477         return (dist <= tolerance);
478 }
479
480 Vector Ellipse::getNearestCenter(const Vector & coord, double * dist)
481 {
482         if (dist != NULL)
483                 *dist = coord.distanceTo(data.center);
484         return data.center;
485 }
486
487 /**
488  * @todo Implement this.
489  */
490 Vector Ellipse::getNearestMiddle(const Vector & /*coord*/, double * dist)
491 {
492         if (dist != NULL)
493                 *dist = RS_MAXDOUBLE;
494         return Vector(false);
495 }
496
497 Vector Ellipse::getNearestDist(double /*distance*/, const Vector & /*coord*/, double * dist)
498 {
499         if (dist != NULL)
500                 *dist = RS_MAXDOUBLE;
501         return Vector(false);
502 }
503
504 double Ellipse::getDistanceToPoint(const Vector & coord, Entity * * entity, RS2::ResolveLevel, double /*solidDist*/)
505 {
506         double dist = RS_MAXDOUBLE;
507         getNearestPointOnEntity(coord, true, &dist, entity);
508
509         return dist;
510 }
511
512 void Ellipse::move(Vector offset)
513 {
514         data.center.move(offset);
515         //calculateEndpoints();
516         calculateBorders();
517 }
518
519 void Ellipse::rotate(Vector center, double angle)
520 {
521         data.center.rotate(center, angle);
522         data.majorP.rotate(angle);
523         //calculateEndpoints();
524         calculateBorders();
525 }
526
527 void Ellipse::scale(Vector center, Vector factor)
528 {
529         data.center.scale(center, factor);
530         data.majorP.scale(factor);
531         //calculateEndpoints();
532         calculateBorders();
533 }
534
535 /**
536  * @todo deal with angles correctly
537  */
538 void Ellipse::mirror(Vector axisPoint1, Vector axisPoint2)
539 {
540         Vector mp = data.center + data.majorP;
541
542         data.center.mirror(axisPoint1, axisPoint2);
543         mp.mirror(axisPoint1, axisPoint2);
544
545         data.majorP = mp - data.center;
546
547         double a = axisPoint1.angleTo(axisPoint2);
548
549         Vector vec;
550         vec.setPolar(1.0, data.angle1);
551         vec.mirror(Vector(0.0, 0.0), axisPoint2 - axisPoint1);
552         data.angle1 = vec.angle() - 2 * a;
553
554         vec.setPolar(1.0, data.angle2);
555         vec.mirror(Vector(0.0, 0.0), axisPoint2 - axisPoint1);
556         data.angle2 = vec.angle() - 2 * a;
557
558         data.reversed = (!data.reversed);
559
560         //calculateEndpoints();
561         calculateBorders();
562 }
563
564 void Ellipse::moveRef(const Vector & ref, const Vector & offset)
565 {
566         Vector startpoint = getStartpoint();
567         Vector endpoint = getEndpoint();
568
569         if (ref.distanceTo(startpoint) < 1.0e-4)
570                 moveStartpoint(startpoint + offset);
571
572
573         if (ref.distanceTo(endpoint) < 1.0e-4)
574                 moveEndpoint(endpoint + offset);
575 }
576
577 void Ellipse::draw(PaintInterface * painter, GraphicView * view, double /*patternOffset*/)
578 {
579         if (!painter || !view)
580                 return;
581
582         if (getPen().getLineType() == RS2::SolidLine || isSelected()
583             || view->getDrawingMode() == RS2::ModePreview)
584                 painter->drawEllipse(view->toGui(getCenter()),
585                         getMajorRadius() * view->getFactor().x,
586                         getMinorRadius() * view->getFactor().x,
587                         getAngle(), getAngle1(), getAngle2(), isReversed());
588         else
589         {
590                 double styleFactor = getStyleFactor(view);
591
592                 if (styleFactor < 0.0)
593                 {
594                         painter->drawEllipse(view->toGui(getCenter()),
595                                 getMajorRadius() * view->getFactor().x,
596                                 getMinorRadius() * view->getFactor().x,
597                                 getAngle(), getAngle1(), getAngle2(), isReversed());
598                         return;
599                 }
600
601                 // Pattern:
602                 LineTypePattern * pat;
603
604                 if (isSelected())
605                         pat = &patternSelected;
606                 else
607                         pat = view->getPattern(getPen().getLineType());
608
609                 if (pat == NULL)
610                         return;
611
612                 // Pen to draw pattern is always solid:
613                 Pen pen = painter->getPen();
614                 pen.setLineType(RS2::SolidLine);
615                 painter->setPen(pen);
616
617                 double * da;     // array of distances in x.
618                 int i;          // index counter
619
620                 double length = getAngleLength();
621
622                 // create pattern:
623                 da = new double[pat->num];
624
625                 double tot = 0.0;
626                 i = 0;
627                 bool done = false;
628                 double curA = getAngle1();
629                 double curR;
630                 Vector cp = view->toGui(getCenter());
631                 double r1 = getMajorRadius() * view->getFactor().x;
632                 double r2 = getMinorRadius() * view->getFactor().x;
633
634                 do
635                 {
636                         curR = sqrt(Math::pow(getMinorRadius() * cos(curA), 2.0)
637                                         + Math::pow(getMajorRadius() * sin(curA), 2.0));
638
639                         if (curR > 1.0e-6)
640                         {
641                                 da[i] = fabs(pat->pattern[i] * styleFactor) / curR;
642
643                                 if (pat->pattern[i] * styleFactor > 0.0)
644                                 {
645                                         if (tot + fabs(da[i]) < length)
646                                                 painter->drawEllipse(cp, r1, r2, getAngle(), curA, curA + da[i], false);
647                                         else
648                                                 painter->drawEllipse(cp, r1, r2, getAngle(), curA, getAngle2(), false);
649                                 }
650                         }
651
652                         curA += da[i];
653                         tot += fabs(da[i]);
654                         done = tot > length;
655
656                         i++;
657
658                         if (i >= pat->num)
659                                 i = 0;
660                 }
661                 while (!done);
662
663                 delete[] da;
664         }
665 }
666
667 /**
668  * Dumps the point's data to stdout.
669  */
670 std::ostream & operator<<(std::ostream & os, const Ellipse & a)
671 {
672         os << " Ellipse: " << a.data << "\n";
673         return os;
674 }
675