]> Shamusworld >> Repos - architektonas/blob - src/arc.cpp
Trim tool now works for Lines, but inaccurate.
[architektonas] / src / arc.cpp
1 // arc.cpp: Arc object
2 //
3 // Part of the Architektonas Project
4 // (C) 2011 Underground Software
5 // See the README and GPLv3 files for licensing and warranty information
6 //
7 // JLH = James Hammons <jlhamm@acm.org>
8 //
9 // WHO  WHEN        WHAT
10 // ---  ----------  ------------------------------------------------------------
11 // JLH  03/30/2011  Created this file
12 // JLH  04/03/2011  Added information panel (angles) rendering
13 // JLH  08/16/2013  Added continuous user feedack like for Line and Circle
14 //
15
16 #include "arc.h"
17
18 #include <QtGui>
19 #include "geometry.h"
20 #include "mathconstants.h"
21 #include "painter.h"
22
23
24 Arc::Arc(Vector p1, double r, double a1, double a2, Object * p/*= NULL*/):
25         Object(p1, p), /*type(OTArc),*/ radius(r), startAngle(a1), angleSpan(a2),
26         draggingCenter(false), draggingEdge(false), draggingRotate(false),
27         draggingSpan(false),
28         hitCenter(false), hitArc(false), hitRotate(false), hitSpan(false)
29 {
30         // This is in the base class, why can't we use the contructor to fill it???
31         type = OTArc;
32         state = OSInactive;
33 }
34
35
36 Arc::~Arc()
37 {
38 }
39
40
41 /*virtual*/ void Arc::Draw(Painter * painter)
42 {
43         QPen pen;
44         painter->SetPen(QPen(Qt::red, 2.0, Qt::DotLine));
45
46         Point p1(cos(startAngle), sin(startAngle));
47         Point p2(cos(startAngle + angleSpan), sin(startAngle + angleSpan));
48         Vector handle2 = (p1 * radius) + position;
49         Vector handle3 = (p2 * radius) + position;
50
51         if ((state == OSSelected) || ((state == OSInactive) && hitRotate))
52                 painter->DrawHandle(handle2);
53
54         if ((state == OSSelected) || ((state == OSInactive) && hitSpan))
55                 painter->DrawHandle(handle3);
56
57         if ((state == OSSelected) || ((state == OSInactive) && hitCenter))
58                 painter->DrawHandle(position);
59
60         if ((state == OSInactive) && !hitArc)
61                 painter->SetPen(QPen(Qt::black, 1.0, Qt::SolidLine));
62
63         painter->DrawArc(position, radius, startAngle, angleSpan);
64
65         if (draggingRotate || draggingSpan)
66         {
67                 if (draggingRotate)
68                 {
69                         // If we rotating, we draw a guideline showing the angle we're
70                         // moving it from.
71                         Point p3(cos(oldAngle), sin(oldAngle));
72                         Vector oldLine = (p3 * (radius * 1.25)) + position;
73                         pen = QPen(QColor(0x80, 0x80, 0x80), 1.0, Qt::DashLine);
74                         painter->SetPen(pen);
75 //                      painter->DrawLine((int)position.x, (int)position.y, (int)oldLine.x, (int)oldLine.y);
76                         painter->DrawLine(position, oldLine);
77                 }
78
79                 // In rotating and setting the span, we draw a line showing where
80                 // we angle/span is that we're setting.
81                 pen = QPen(QColor(0x00, 0xC0, 0x80), 1.0, Qt::DashLine);
82                 painter->SetPen(pen);
83                 painter->DrawLine((int)position.x, (int)position.y, (int)oldPoint.x, (int)oldPoint.y);
84         }
85
86         // If we're rotating or setting the span, draw an information panel
87         // showing both absolute and relative angles being set.
88         if (draggingRotate || draggingSpan || draggingEdge)
89         {
90                 double absAngle = (Vector(oldPoint - position).Angle()) * RADIANS_TO_DEGREES;
91                 double relAngle = (startAngle >= oldAngle ? startAngle - oldAngle :
92                         startAngle - oldAngle + (2.0 * PI)) * RADIANS_TO_DEGREES;
93
94                 QString text;
95
96                 if (draggingRotate)
97                 {
98                         text = QObject::tr("Abs ") + QChar(0x2221) + ": %1" + QChar(0x00B0)
99                                 + QObject::tr("\nRel ") + QChar(0x2221) + ": %2" + QChar(0x00B0);
100                         text = text.arg(absAngle, 0, 'd', 4).arg(relAngle, 0, 'd', 4);
101                 }
102                 else if (draggingSpan)
103                 {
104                         text = QObject::tr("Abs ") + QChar(0x2221) + ": %1" + QChar(0x00B0)
105                                 + QObject::tr("\nSpan: %2") + QChar(0x00B0);
106                         text = text.arg(absAngle, 0, 'd', 4).arg(angleSpan * RADIANS_TO_DEGREES, 0, 'd', 4);
107                 }
108                 else if (draggingEdge)
109                 {
110                         text = QObject::tr("Radius: %1\nScale: %2%");
111                         text = text.arg(radius, 0, 'd', 4).arg(radius / oldRadius * 100.0, 0, 'd', 0);
112                 }
113
114 #if 0
115                 pen = QPen(QColor(0x00, 0xFF, 0x00), 1.0, Qt::SolidLine);
116                 painter->SetPen(pen);
117                 painter->SetBrush(QBrush(QColor(0x40, 0xFF, 0x40, 0x9F)));
118                 QRectF textRect(10.0, 10.0, 270.0, 70.0);       // x, y, w, h
119                 painter->DrawRoundedRect(textRect, 7.0, 7.0);
120
121                 textRect.setLeft(textRect.left() + 14);
122                 painter->SetFont(*Object::font);
123                 pen = QPen(QColor(0x00, 0x5F, 0xDF));
124                 painter->SetPen(pen);
125                 painter->DrawText(textRect, Qt::AlignVCenter, text);
126 #else
127                 painter->DrawInformativeText(text);
128 #endif
129         }
130 }
131
132
133 /*virtual*/ Vector Arc::Center(void)
134 {
135         return position;
136 }
137
138
139 /*
140  We need at least *four* handles for this object:
141  - one for moving
142  - one for resizing
143  - one for rotation
144  - one for setting the span of the arc
145
146 We need to think about the intuitive way (if there is any) to grab and
147 manipulate a complex object like this... Need to think, "What should happen when
148 I click here and drag there?"
149
150 Also: should put the snap logic into the Object base class (as a static method)...
151 */
152
153 /*virtual*/ bool Arc::Collided(Vector point)
154 {
155         // Someone told us to fuck off, so we'll fuck off. :-)
156         if (ignoreClicks)
157                 return false;
158
159         objectWasDragged = false;
160         bool hitSomething = HitTest(point);
161         draggingCenter = hitCenter;
162         draggingEdge   = hitArc;
163         draggingRotate = hitRotate;
164         draggingSpan   = hitSpan;
165
166         // Now that we've done our hit testing on the non-snapped point, snap it if
167         // necessary...
168         if (snapToGrid)
169                 point = SnapPointToGrid(point);
170
171 /*
172 State Management:
173 We want the arc to go into OSSelected mode if we click on it but don't drag.
174 If we don't click anywhere on the arc, then we want it to go into OSInactive mode.
175 Otherwise, we hit a handle and we want to modify the object whether it's in
176 OSSelected mode or not.
177
178 If I, go to S. Can drag, but only handles 2, 3, & 4 (since the center is not highlighted
179 until we select it.
180         However, if dragging, revert to I once finished.
181
182 If S, stay in S. Can drag all.
183
184 So, we know this. If wasDragged is true, then there's a chance we need to revert to I.
185 How can we be sure?
186
187 So. We have a matrix that looks like this:
188
189         |Was I|Was S
190 --------+-----+-----
191 Inactive|     |   
192 --------+-----+-----
193 Selected|     |   
194
195         |h1|h2|h3|h4
196 --------+--+--+--+--
197 Inactive|  |  |  |
198 --------+--+--+--+--
199 Selected|  |  |  |
200
201 so let's do like this:
202 */
203         if (hitSomething)
204         {
205                 oldState = state;
206                 state = OSSelected;
207 //              oldPoint = point;
208                 oldPoint = position;
209                 oldAngle = startAngle;
210                 oldRadius = radius;
211                 return true;
212         }
213
214         state = OSInactive;
215         return false;
216 }
217
218
219 /*virtual*/ bool Arc::PointerMoved(Vector point)
220 {
221 // one other thing to check here for is if a modifier key is being held as well,
222 // to allow for multi-selection
223         if (selectionInProgress)
224         {
225                 // Check for whether or not the rect contains this circle
226 //              if (selection.normalized().contains(Extents()))
227                 if (selection.contains(Extents()))
228                         state = OSSelected;
229                 else
230                         state = OSInactive;
231
232                 return false;
233         }
234
235         // The TLC will send these messages if the object is selected but not clicked on.
236         // So we have to be careful with our assumptions here.
237         // This is actually untrue in that case, we need to come up with something better
238         // here...
239 //      objectWasDragged = true;
240 //      needUpdate = false;
241         SaveHitState();
242         bool hovered = HitTest(point);
243         needUpdate = HitStateChanged();
244         objectWasDragged = (draggingCenter | draggingEdge | draggingRotate | draggingSpan);
245
246         if (objectWasDragged)
247                 needUpdate = true;
248 //      if (!(hitHandle1 || hitHandle2 || hitHandle3 || hitHandle4))
249 //              return;
250
251 //      Vector delta = point - oldPoint;
252
253         if (draggingCenter)
254                 position = point;
255         else if (draggingEdge)
256                 radius = Vector::Magnitude(point, position);
257         else if (draggingRotate)
258         {
259                 startAngle = Vector(point - position).Angle();
260         }
261         else if (draggingSpan)
262         {
263                 double angle = Vector(point - position).Angle();
264
265                 if (angle < startAngle)
266                         angle += 2.0 * PI;
267
268                 angleSpan = angle - startAngle;
269         }
270
271         // Why save this? For rendering code?
272         oldPoint = point;
273 //      needUpdate = true;
274         return hovered;
275 }
276
277
278 /*virtual*/ void Arc::PointerReleased(void)
279 {
280         // Mouse went up, so our dragging is done (if any *was* done, that is)
281 //      hitHandle1 = hitHandle2 = hitHandle3 = hitHandle4 = false;
282         draggingCenter = draggingEdge = draggingRotate = draggingSpan = false;
283         hitCenter = hitArc = hitRotate = hitSpan = false;
284
285         // If the object was dragged, then revert to the old state.
286         // Otherwise, we were probably just clicked, and want to stay in the selected state.
287         if (objectWasDragged)
288                 state = oldState;
289 }
290
291
292 /*virtual*/ bool Arc::HitTest(Point point)
293 {
294         hitCenter = hitArc = hitRotate = hitSpan = false;
295
296 /*
297 What we have:
298 the center of the arc
299 the starting angle
300 the span of the arc
301 The point on a unit circle given an angle a is x = cos(a), y = sin(a)
302 This vector is already unitized, so all we need to do to get our point is to
303 multiply it by radius (to get the length correct) and add it to the center
304 point (to get the correct position).
305 */
306 //      Vector v1(point, position);     // Head minus tail (vector points at "point")
307         Vector v1(position, point);     // Head minus tail (vector points at "point")
308         Point p1(cos(startAngle), sin(startAngle));
309         Point p2(cos(startAngle + angleSpan), sin(startAngle + angleSpan));
310         Vector handle2 = (p1 * radius) + position;
311         Vector handle3 = (p2 * radius) + position;
312         double pointerAngle = v1.Angle();
313         double length = v1.Magnitude();
314
315 #if 0
316         if (v1.Magnitude() < 10.0)
317                 hitCenter = true;
318         else if (Vector(handle3 - point).Magnitude() < 10.0)
319                 hitSpan = true;
320         else if (Vector(handle2 - point).Magnitude() < 10.0)
321                 hitRotate = true;
322         else if ((v1.Magnitude() < radius + 3.0) && (v1.Magnitude() > radius - 3.0)
323                 && AngleInArcSpan(pointerAngle))
324                 hitArc = true;
325 #else
326         if ((length * Painter::zoom) < 8.0)
327                 hitCenter = true;
328         else if (((fabs(length - radius) * Painter::zoom) < 2.0)
329                 && AngleInArcSpan(pointerAngle))
330                 hitArc = true;
331         else if ((Vector::Magnitude(handle2, point) * Painter::zoom) < 8.0)
332                 hitRotate = true;
333         else if ((Vector::Magnitude(handle3, point) * Painter::zoom) < 8.0)
334                 hitSpan = true;
335 #endif
336
337         return (hitCenter || hitArc || hitRotate || hitSpan ? true : false);
338 }
339
340
341 /*virtual*/ QRectF Arc::Extents(void)
342 {
343         double start = startAngle;
344         double end = start + angleSpan;
345         QPointF p1(cos(start), sin(start));
346         QPointF p2(cos(end), sin(end));
347         QRectF bounds(p1, p2);
348
349         // Swap X/Y coordinates if they're backwards...
350         if (bounds.left() > bounds.right())
351         {
352                 double temp = bounds.left();
353                 bounds.setLeft(bounds.right());
354                 bounds.setRight(temp);
355         }
356
357         if (bounds.bottom() > bounds.top())
358         {
359                 double temp = bounds.bottom();
360                 bounds.setBottom(bounds.top());
361                 bounds.setTop(temp);
362         }
363
364         // If the end of the arc is before the beginning, add 360 degrees to it
365         if (end < start)
366                 end += 2.0 * PI;
367
368         // Adjust the bounds depending on which axes are crossed
369         if ((start < PI_OVER_2) && (end > PI_OVER_2))
370                 bounds.setTop(1.0);
371
372         if ((start < PI) && (end > PI))
373                 bounds.setLeft(-1.0);
374
375         if ((start < (PI + PI_OVER_2)) && (end > (PI + PI_OVER_2)))
376                 bounds.setBottom(-1.0);
377
378         if ((start < (2.0 * PI)) && (end > (2.0 * PI)))
379                 bounds.setRight(1.0);
380
381         if ((start < ((2.0 * PI) + PI_OVER_2)) && (end > ((2.0 * PI) + PI_OVER_2)))
382                 bounds.setTop(1.0);
383
384         if ((start < (3.0 * PI)) && (end > (3.0 * PI)))
385                 bounds.setLeft(-1.0);
386
387         if ((start < ((3.0 * PI) + PI_OVER_2)) && (end > ((3.0 * PI) + PI_OVER_2)))
388                 bounds.setBottom(-1.0);
389
390         bounds.setTopLeft(QPointF(bounds.left() * radius, bounds.top() * radius));
391         bounds.setBottomRight(QPointF(bounds.right() * radius, bounds.bottom() * radius));
392         bounds.translate(position.x, position.y);
393         return bounds;
394 }
395
396
397 /*
398 start = 350, span = 20, end = 10, angle = 5
399 angle < start, so angle = 365
400 */
401 bool Arc::AngleInArcSpan(double angle)
402 {
403         // This should be simple except for a small complication: The start angle plus
404         // the angle span can end up being less than the start angle! So, to check
405         // for this possibility, we check to see if the angle passed in is less than
406         // the start angle and if so, add 2PI to the angle passed in. Then we take the
407         // difference between our start angle and this adjusted angle and see if it
408         // falls within our span.
409         if (angle < startAngle)
410                 angle += 2.0 * PI;
411
412         double passedInSpan = angle - startAngle;
413
414         return (passedInSpan <= angleSpan ?  true : false);
415 }
416
417
418 void Arc::SaveHitState(void)
419 {
420         oldHitCenter = hitCenter;
421         oldHitArc    = hitArc;
422         oldHitRotate = hitRotate;
423         oldHitSpan   = hitSpan;
424 }
425
426
427 bool Arc::HitStateChanged(void)
428 {
429         if ((hitCenter != oldHitCenter)
430                 || (hitArc != oldHitArc)
431                 || (hitRotate != oldHitRotate)
432                 || (hitSpan != oldHitSpan))
433                 return true;
434
435         return false;
436 }
437
438
439 /*virtual*/ void Arc::Enumerate(FILE * file)
440 {
441         fprintf(file, "ARC %i (%lf,%lf) %lf, %lf, %lf\n", layer, position.x, position.y, radius, startAngle, angleSpan);
442 }
443
444
445 /*virtual*/ Object * Arc::Copy(void)
446 {
447 #warning "!!! This doesn't take care of attached Dimensions !!!"
448 /*
449 This is a real problem. While having a pointer in the Dimension to this line's points
450 is fast & easy, it creates a huge problem when trying to replicate an object like this.
451
452 Maybe a way to fix that then, is to have reference numbers instead of pointers. That
453 way, if you copy them, ... you might still have problems. Because you can't be sure if
454 a copy will be persistant or not, you then *definitely* do not want them to have the
455 same reference number.
456 */
457         return new Arc(position, radius, startAngle, angleSpan, parent);
458 }
459
460
461 /*virtual*/ void Arc::Rotate(Point point, double angle)
462 {
463         Point c1 = Geometry::RotatePointAroundPoint(position, point, angle);
464         Point ap1(cos(startAngle), sin(startAngle));
465         Point angleStartPoint = (ap1 * radius) + position;
466         Point c2 = Geometry::RotatePointAroundPoint(angleStartPoint, point, angle);
467
468         position = c1;
469         startAngle = Vector(c2, c1).Angle();
470 }
471
472
473 /*virtual*/ void Arc::Mirror(Point p1, Point p2)
474 {
475         Point c1 = Geometry::MirrorPointAroundLine(position, p1, p2);
476         Point ap1(cos(startAngle + angleSpan), sin(startAngle + angleSpan));
477         Point angleEndPoint = (ap1 * radius) + position;
478         Point c2 = Geometry::MirrorPointAroundLine(angleEndPoint, p1, p2);
479
480         position = c1;
481         startAngle = Vector(c2, c1).Angle();
482 }
483
484
485 /*virtual*/ void Arc::Save(void)
486 {
487         Object::Save();
488         oldRadius2 = radius;
489         oldStartAngle = startAngle;
490         oldAngleSpan = angleSpan;
491 }
492
493
494 /*virtual*/ void Arc::Restore(void)
495 {
496         Object::Restore();
497         radius = oldRadius2;
498         startAngle = oldStartAngle;
499         angleSpan = oldAngleSpan;
500 }
501