]> Shamusworld >> Repos - architektonas/blob - src/arc.cpp
Lines respond to mouse movement, added Text rendering.
[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         if (snapPointIsValid)
172                 point = snapPoint;
173
174 /*
175 State Management:
176 We want the arc to go into OSSelected mode if we click on it but don't drag.
177 If we don't click anywhere on the arc, then we want it to go into OSInactive mode.
178 Otherwise, we hit a handle and we want to modify the object whether it's in
179 OSSelected mode or not.
180
181 If I, go to S. Can drag, but only handles 2, 3, & 4 (since the center is not highlighted
182 until we select it.
183         However, if dragging, revert to I once finished.
184
185 If S, stay in S. Can drag all.
186
187 So, we know this. If wasDragged is true, then there's a chance we need to revert to I.
188 How can we be sure?
189
190 So. We have a matrix that looks like this:
191
192         |Was I|Was S
193 --------+-----+-----
194 Inactive|     |   
195 --------+-----+-----
196 Selected|     |   
197
198         |h1|h2|h3|h4
199 --------+--+--+--+--
200 Inactive|  |  |  |
201 --------+--+--+--+--
202 Selected|  |  |  |
203
204 so let's do like this:
205 */
206         if (hitSomething)
207         {
208                 oldState = state;
209                 state = OSSelected;
210 //              oldPoint = point;
211                 oldPoint = position;
212                 oldAngle = startAngle;
213                 oldRadius = radius;
214                 return true;
215         }
216
217         state = OSInactive;
218         return false;
219 }
220
221
222 /*virtual*/ bool Arc::PointerMoved(Vector point)
223 {
224 // one other thing to check here for is if a modifier key is being held as well,
225 // to allow for multi-selection
226         if (selectionInProgress)
227         {
228                 // Check for whether or not the rect contains this circle
229 //              if (selection.normalized().contains(Extents()))
230                 if (selection.contains(Extents()))
231                         state = OSSelected;
232                 else
233                         state = OSInactive;
234
235                 return false;
236         }
237
238         // The TLC will send these messages if the object is selected but not clicked on.
239         // So we have to be careful with our assumptions here.
240         // This is actually untrue in that case, we need to come up with something better
241         // here...
242 //      objectWasDragged = true;
243 //      needUpdate = false;
244         SaveHitState();
245         bool hovered = HitTest(point);
246         needUpdate = HitStateChanged();
247
248         if (snapToGrid)
249                 point = SnapPointToGrid(point);
250
251         if (snapPointIsValid)
252                 point = snapPoint;
253
254         objectWasDragged = (draggingCenter | draggingEdge | draggingRotate | draggingSpan);
255
256         if (objectWasDragged)
257                 needUpdate = true;
258 //      if (!(hitHandle1 || hitHandle2 || hitHandle3 || hitHandle4))
259 //              return;
260
261 //      Vector delta = point - oldPoint;
262
263         if (draggingCenter)
264                 position = point;
265         else if (draggingEdge)
266                 radius = Vector::Magnitude(point, position);
267         else if (draggingRotate)
268         {
269                 startAngle = Vector(point - position).Angle();
270         }
271         else if (draggingSpan)
272         {
273                 double angle = Vector(point - position).Angle();
274
275                 if (angle < startAngle)
276                         angle += 2.0 * PI;
277
278                 angleSpan = angle - startAngle;
279         }
280
281         // Why save this? For rendering code?
282         oldPoint = point;
283 //      needUpdate = true;
284         return hovered;
285 }
286
287
288 /*virtual*/ void Arc::PointerReleased(void)
289 {
290         // Mouse went up, so our dragging is done (if any *was* done, that is)
291 //      hitHandle1 = hitHandle2 = hitHandle3 = hitHandle4 = false;
292         draggingCenter = draggingEdge = draggingRotate = draggingSpan = false;
293         hitCenter = hitArc = hitRotate = hitSpan = false;
294
295         // If the object was dragged, then revert to the old state.
296         // Otherwise, we were probably just clicked, and want to stay in the selected state.
297         if (objectWasDragged)
298                 state = oldState;
299 }
300
301
302 /*virtual*/ bool Arc::HitTest(Point point)
303 {
304         hitCenter = hitArc = hitRotate = hitSpan = false;
305
306 /*
307 What we have:
308 the center of the arc
309 the starting angle
310 the span of the arc
311 The point on a unit circle given an angle a is x = cos(a), y = sin(a)
312 This vector is already unitized, so all we need to do to get our point is to
313 multiply it by radius (to get the length correct) and add it to the center
314 point (to get the correct position).
315 */
316 //      Vector v1(point, position);     // Head minus tail (vector points at "point")
317         Vector v1(position, point);     // Head minus tail (vector points at "point")
318         Point p1(cos(startAngle), sin(startAngle));
319         Point p2(cos(startAngle + angleSpan), sin(startAngle + angleSpan));
320         Vector handle2 = (p1 * radius) + position;
321         Vector handle3 = (p2 * radius) + position;
322         double pointerAngle = v1.Angle();
323         double length = v1.Magnitude();
324
325 #if 0
326         if (v1.Magnitude() < 10.0)
327                 hitCenter = true;
328         else if (Vector(handle3 - point).Magnitude() < 10.0)
329                 hitSpan = true;
330         else if (Vector(handle2 - point).Magnitude() < 10.0)
331                 hitRotate = true;
332         else if ((v1.Magnitude() < radius + 3.0) && (v1.Magnitude() > radius - 3.0)
333                 && AngleInArcSpan(pointerAngle))
334                 hitArc = true;
335 #else
336         if ((length * Painter::zoom) < 8.0)
337         {
338                 hitCenter = true;
339                 snapPoint = position;
340                 snapPointIsValid = true;
341         }
342         else if (((fabs(length - radius) * Painter::zoom) < 2.0)
343                 && AngleInArcSpan(pointerAngle))
344                 hitArc = true;
345         else if ((Vector::Magnitude(handle2, point) * Painter::zoom) < 8.0)
346         {
347                 hitRotate = true;
348                 snapPoint = handle2;
349                 snapPointIsValid = true;
350         }
351         else if ((Vector::Magnitude(handle3, point) * Painter::zoom) < 8.0)
352         {
353                 hitSpan = true;
354                 snapPoint = handle3;
355                 snapPointIsValid = true;
356         }
357 #endif
358
359         return (hitCenter || hitArc || hitRotate || hitSpan ? true : false);
360 }
361
362
363 /*virtual*/ QRectF Arc::Extents(void)
364 {
365         double start = startAngle;
366         double end = start + angleSpan;
367         QPointF p1(cos(start), sin(start));
368         QPointF p2(cos(end), sin(end));
369         QRectF bounds(p1, p2);
370
371 #if 0
372         // Swap X/Y coordinates if they're backwards...
373         if (bounds.left() > bounds.right())
374         {
375                 double temp = bounds.left();
376                 bounds.setLeft(bounds.right());
377                 bounds.setRight(temp);
378         }
379
380         if (bounds.bottom() > bounds.top())
381         {
382                 double temp = bounds.bottom();
383                 bounds.setBottom(bounds.top());
384                 bounds.setTop(temp);
385         }
386 #else
387         bounds = bounds.normalized();
388 #endif
389
390         // If the end of the arc is before the beginning, add 360 degrees to it
391         if (end < start)
392                 end += 2.0 * PI;
393
394         // Adjust the bounds depending on which axes are crossed
395         if ((start < PI_OVER_2) && (end > PI_OVER_2))
396                 bounds.setTop(1.0);
397
398         if ((start < PI) && (end > PI))
399                 bounds.setLeft(-1.0);
400
401         if ((start < (PI + PI_OVER_2)) && (end > (PI + PI_OVER_2)))
402                 bounds.setBottom(-1.0);
403
404         if ((start < (2.0 * PI)) && (end > (2.0 * PI)))
405                 bounds.setRight(1.0);
406
407         if ((start < ((2.0 * PI) + PI_OVER_2)) && (end > ((2.0 * PI) + PI_OVER_2)))
408                 bounds.setTop(1.0);
409
410         if ((start < (3.0 * PI)) && (end > (3.0 * PI)))
411                 bounds.setLeft(-1.0);
412
413         if ((start < ((3.0 * PI) + PI_OVER_2)) && (end > ((3.0 * PI) + PI_OVER_2)))
414                 bounds.setBottom(-1.0);
415
416         bounds.setTopLeft(QPointF(bounds.left() * radius, bounds.top() * radius));
417         bounds.setBottomRight(QPointF(bounds.right() * radius, bounds.bottom() * radius));
418         bounds.translate(position.x, position.y);
419         return bounds;
420 }
421
422
423 /*
424 start = 350, span = 20, end = 10, angle = 5
425 angle < start, so angle = 365
426 */
427 bool Arc::AngleInArcSpan(double angle)
428 {
429         // This should be simple except for a small complication: The start angle plus
430         // the angle span can end up being less than the start angle! So, to check
431         // for this possibility, we check to see if the angle passed in is less than
432         // the start angle and if so, add 2PI to the angle passed in. Then we take the
433         // difference between our start angle and this adjusted angle and see if it
434         // falls within our span.
435         if (angle < startAngle)
436                 angle += 2.0 * PI;
437
438         double passedInSpan = angle - startAngle;
439
440         return (passedInSpan <= angleSpan ?  true : false);
441 }
442
443
444 void Arc::SaveHitState(void)
445 {
446         oldHitCenter = hitCenter;
447         oldHitArc    = hitArc;
448         oldHitRotate = hitRotate;
449         oldHitSpan   = hitSpan;
450 }
451
452
453 bool Arc::HitStateChanged(void)
454 {
455         if ((hitCenter != oldHitCenter)
456                 || (hitArc != oldHitArc)
457                 || (hitRotate != oldHitRotate)
458                 || (hitSpan != oldHitSpan))
459                 return true;
460
461         return false;
462 }
463
464
465 /*virtual*/ void Arc::Enumerate(FILE * file)
466 {
467         fprintf(file, "ARC %i (%lf,%lf) %lf, %lf, %lf\n", layer, position.x, position.y, radius, startAngle, angleSpan);
468 }
469
470
471 /*virtual*/ Object * Arc::Copy(void)
472 {
473 #warning "!!! This doesn't take care of attached Dimensions !!!"
474 /*
475 This is a real problem. While having a pointer in the Dimension to this line's points
476 is fast & easy, it creates a huge problem when trying to replicate an object like this.
477
478 Maybe a way to fix that then, is to have reference numbers instead of pointers. That
479 way, if you copy them, ... you might still have problems. Because you can't be sure if
480 a copy will be persistant or not, you then *definitely* do not want them to have the
481 same reference number.
482 */
483         return new Arc(position, radius, startAngle, angleSpan, parent);
484 }
485
486
487 /*virtual*/ void Arc::Rotate(Point point, double angle)
488 {
489         Point c1 = Geometry::RotatePointAroundPoint(position, point, angle);
490         Point ap1(cos(startAngle), sin(startAngle));
491         Point angleStartPoint = (ap1 * radius) + position;
492         Point c2 = Geometry::RotatePointAroundPoint(angleStartPoint, point, angle);
493
494         position = c1;
495         startAngle = Vector(c1, c2).Angle();
496 }
497
498
499 /*virtual*/ void Arc::Mirror(Point p1, Point p2)
500 {
501         Point c1 = Geometry::MirrorPointAroundLine(position, p1, p2);
502         Point ap1(cos(startAngle + angleSpan), sin(startAngle + angleSpan));
503         Point angleEndPoint = (ap1 * radius) + position;
504         Point c2 = Geometry::MirrorPointAroundLine(angleEndPoint, p1, p2);
505
506         position = c1;
507         startAngle = Vector(c2, c1).Angle();
508 }
509
510
511 /*virtual*/ void Arc::Save(void)
512 {
513         Object::Save();
514         oldRadius2 = radius;
515         oldStartAngle = startAngle;
516         oldAngleSpan = angleSpan;
517 }
518
519
520 /*virtual*/ void Arc::Restore(void)
521 {
522         Object::Restore();
523         radius = oldRadius2;
524         startAngle = oldStartAngle;
525         angleSpan = oldAngleSpan;
526 }
527