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