]> Shamusworld >> Repos - architektonas/blob - src/arc.cpp
355d7a76f2b5b425ddc5b882f7e21e86a22d8dc4
[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 //
14
15 #include "arc.h"
16
17 #include <QtGui>
18 #include "mathconstants.h"
19 #include "painter.h"
20
21
22 Arc::Arc(Vector p1, double r, double a1, double a2, Object * p/*= NULL*/):
23         Object(p1, p), /*type(OTArc),*/ radius(r), startAngle(a1), angleSpan(a2)
24 {
25         // This is in the base class, why can't we use the contructor to fill it???
26         type = OTArc;
27 }
28
29
30 Arc::~Arc()
31 {
32 }
33
34
35 /*virtual*/ void Arc::Draw(Painter * painter)
36 {
37         QPen pen;
38
39         if (state == OSSelected)
40         {
41                 Point p1(cos(startAngle), sin(startAngle));
42                 Point p2(cos(startAngle + angleSpan), sin(startAngle + angleSpan));
43                 Vector handle2 = (p1 * radius) + position;
44                 Vector handle3 = (p2 * radius) + position;
45
46                 if ((hitHandle2 || hitHandle3) && objectWasDragged)
47                 {
48                         if (hitHandle2)
49                         {
50                                 // If we rotating, we draw a guideline showing the angle we're
51                                 // moving it from.
52                                 Point p3(cos(oldAngle), sin(oldAngle));
53                                 Vector oldLine = (p3 * (radius * 1.25)) + position;
54                                 pen = QPen(QColor(0x80, 0x80, 0x80), 1.0, Qt::DashLine);
55                                 painter->SetPen(pen);
56                                 painter->DrawLine((int)position.x, (int)position.y, (int)oldLine.x, (int)oldLine.y);
57                         }
58
59                         // In rotating and setting the span, we draw a line showing where
60                         // we angle/span is that we're setting.
61                         pen = QPen(QColor(0x00, 0xC0, 0x80), 1.0, Qt::DashLine);
62                         painter->SetPen(pen);
63                         painter->DrawLine((int)position.x, (int)position.y, (int)oldPoint.x, (int)oldPoint.y);
64                 }
65
66                 // Draw the center point of the arc
67                 painter->SetPen(QPen(Qt::red, 2.0, Qt::DotLine));
68                 painter->DrawHandle(position);
69
70                 // Draw the rotation & span setting handles
71                 painter->DrawHandle(handle2);
72                 painter->DrawHandle(handle3);
73
74                 // If we're rotating or setting the span, draw an information panel
75                 // showing both absolute and relative angles being set.
76                 if ((hitHandle2 || hitHandle3 || hitHandle4) && objectWasDragged)
77                 {
78                         double absAngle = (Vector(oldPoint - position).Angle()) * RADIANS_TO_DEGREES;
79                         double relAngle = (startAngle >= oldAngle ? startAngle - oldAngle :
80                                 startAngle - oldAngle + (2.0 * PI)) * RADIANS_TO_DEGREES;
81
82                         QString text;
83
84                         if (hitHandle2)
85                         {
86                                 text = QObject::tr("Abs ") + QChar(0x2221) + ": %1" + QChar(0x00B0)
87                                         + QObject::tr("\nRel ") + QChar(0x2221) + ": %2" + QChar(0x00B0);
88                                 text = text.arg(absAngle, 0, 'd', 4).arg(relAngle, 0, 'd', 4);
89                         }
90                         else if (hitHandle3)
91                         {
92                                 text = QObject::tr("Abs ") + QChar(0x2221) + ": %1" + QChar(0x00B0)
93                                         + QObject::tr("\nSpan: %2") + QChar(0x00B0);
94                                 text = text.arg(absAngle, 0, 'd', 4).arg(angleSpan * RADIANS_TO_DEGREES, 0, 'd', 4);
95                         }
96                         else if (hitHandle4)
97                         {
98                                 text = QObject::tr("Radius: %1\nScale: %2%");
99                                 text = text.arg(radius, 0, 'd', 4).arg(radius / oldRadius * 100.0, 0, 'd', 0);
100                         }
101
102                         pen = QPen(QColor(0x00, 0xFF, 0x00), 1.0, Qt::SolidLine);
103                         painter->SetPen(pen);
104                         painter->SetBrush(QBrush(QColor(0x40, 0xFF, 0x40, 0x9F)));
105                         QRectF textRect(10.0, 10.0, 270.0, 70.0);       // x, y, w, h
106                         painter->DrawRoundedRect(textRect, 7.0, 7.0);
107
108                         textRect.setLeft(textRect.left() + 14);
109                         painter->SetFont(*Object::font);
110 //                      pen = QPen(QColor(0xDF, 0x5F, 0x00), 1.0, Qt::SolidLine);
111                         pen = QPen(QColor(0x00, 0x5F, 0xDF));
112                         painter->SetPen(pen);
113                         painter->DrawText(textRect, Qt::AlignVCenter, text);
114                         painter->SetPen(QPen(QColor(0xDF, 0x5F, 0x00)));
115                 }
116         }
117         else
118         {
119                 pen = QPen(Qt::black, 1.0, Qt::SolidLine);
120                 painter->SetPen(pen);
121         }
122
123         painter->DrawArc(position, radius, startAngle, angleSpan);
124 //      painter->DrawRect(Extents());
125 }
126
127
128 /*virtual*/ Vector Arc::Center(void)
129 {
130         return position;
131 }
132
133
134 /*
135  We need at least *four* handles for this object:
136  - one for moving
137  - one for resizing
138  - one for rotation
139  - one for setting the span of the arc
140
141 We need to think about the intuitive way (if there is any) to grab and
142 manipulate a complex object like this... Need to think, "What should happen when
143 I click here and drag there?"
144
145 Also: should put the snap logic into the Object base class (as a static method)...
146 */
147
148 /*virtual*/ bool Arc::Collided(Vector point)
149 {
150         objectWasDragged = false;
151         Vector v1 = point - position;                   // Head minus tail (vector points at "point")
152
153         // Check for collision with various things...
154         hitHandle1 = false;     // Moving
155         hitHandle2 = false;     // Rotation
156         hitHandle3 = false;     // Setting span of the arc
157         hitHandle4 = false;     // Resizing
158 /*
159 What we have:
160 the center of the arc
161 the starting angle
162 the span of the arc
163 The point on a unit circle given an angle a is x = cos(a), y = sin(a)
164 This vector is already unitized, so all we need to do to get our point is to multiply it by
165 radius (to get the length correct) and add it to the center point (to get the correct position).
166 */
167         Point p1(cos(startAngle), sin(startAngle));
168         Point p2(cos(startAngle + angleSpan), sin(startAngle + angleSpan));
169         Vector handle2 = (p1 * radius) + position;
170         Vector handle3 = (p2 * radius) + position;
171         double pointerAngle = v1.Angle();
172
173 #if 1
174         // Center handle
175         if (v1.Magnitude() < 10.0)
176                 hitHandle1 = true;
177         // Span handle
178         else if (Vector(handle3 - point).Magnitude() < 10.0)
179                 hitHandle3 = true;
180         // Rotate handle
181         else if (Vector(handle2 - point).Magnitude() < 10.0)
182                 hitHandle2 = true;
183         // Resize handle (the arc itself)
184         else if ((v1.Magnitude() < radius + 3.0) && (v1.Magnitude() > radius - 3.0)
185                 && AngleInArcSpan(pointerAngle))
186                 hitHandle4 = true;
187 #endif
188
189 /*
190 State Management:
191 We want the arc to go into OSSelected mode if we click on it but don't drag.
192 If we don't click anywhere on the arc, then we want it to go into OSInactive mode.
193 Otherwise, we hit a handle and we want to modify the object whether it's in
194 OSSelected mode or not.
195
196 If I, go to S. Can drag, but only handles 2, 3, & 4 (since the center is not highlighted
197 until we select it.
198         However, if dragging, revert to I once finished.
199
200 If S, stay in S. Can drag all.
201
202 So, we know this. If wasDragged is true, then there's a chance we need to revert to I.
203 How can we be sure?
204
205 So. We have a matrix that looks like this:
206
207         |Was I|Was S
208 --------+-----+-----
209 Inactive|     |   
210 --------+-----+-----
211 Selected|     |   
212
213         |h1|h2|h3|h4
214 --------+--+--+--+--
215 Inactive|  |  |  |
216 --------+--+--+--+--
217 Selected|  |  |  |
218
219 so let's do like this:
220 */
221         if (hitHandle1 || hitHandle2 || hitHandle3 || hitHandle4)
222         {
223                 oldState = state;
224                 state = OSSelected;
225 //              oldPoint = point;
226                 oldPoint = position;
227                 oldAngle = startAngle;
228                 oldRadius = radius;
229
230                 return true;
231         }
232
233         state = OSInactive;
234         return false;
235 }
236
237
238 /*virtual*/ void Arc::PointerMoved(Vector point)
239 {
240         if (selectionInProgress)
241         {
242                 // Check for whether or not the rect contains this circle
243                 if (selection.normalized().contains(Extents()))
244                         state = OSSelected;
245                 else
246                         state = OSInactive;
247
248                 return;
249         }
250
251         // The TLC will send these messages if the object is selected but not clicked on.
252         // So we have to be careful with our assumptions here.
253         // This is actually untrue in that case, we need to come up with something better
254         // here...
255         objectWasDragged = true;
256         needUpdate = false;
257
258         if (!(hitHandle1 || hitHandle2 || hitHandle3 || hitHandle4))
259                 return;
260
261         Vector delta = point - oldPoint;
262
263         if (hitHandle1)                 // Move arc
264         {
265                 position += delta;
266         }
267         else if (hitHandle2)    // Rotate arc
268         {
269                 startAngle = Vector(point - position).Angle();
270         }
271         else if (hitHandle3)    // Set arc span
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         else if (hitHandle4)    // Resize the radius of the arc
281         {
282                 radius = Vector(point - position).Magnitude();
283         }
284
285         oldPoint = point;
286         needUpdate = true;
287 }
288
289
290 /*virtual*/ void Arc::PointerReleased(void)
291 {
292         hitHandle1 = hitHandle2 = hitHandle3 = hitHandle4 = false;
293         // Here we check for just a click: If object was clicked and dragged, then
294         // revert to the old state (OSInactive). Otherwise, keep the new state that
295         // we set.
296 /*
297 Maybe it would be better to just check for "object was dragged" state and not
298 have to worry about keeping track of old states...
299 Well, we can't know if it was dragged from Inactive or not, that's the problem.
300 We could make a variable called "needToRevertToInactive" instead
301
302 I mean, we could write like:
303         if (objectWasDragged && oldState == OSInactive)
304                 state = OSInactive;
305
306 but this is actually more compact and cleaner.
307 */
308         if (objectWasDragged)
309                 state = oldState;
310 }
311
312
313 /*virtual*/ QRectF Arc::Extents(void)
314 {
315         double start = startAngle;
316         double end = start + angleSpan;
317         QPointF p1(cos(start), sin(start));
318         QPointF p2(cos(end), sin(end));
319         QRectF bounds(p1, p2);
320
321         // Swap X/Y coordinates if they're backwards...
322         if (bounds.left() > bounds.right())
323         {
324                 double temp = bounds.left();
325                 bounds.setLeft(bounds.right());
326                 bounds.setRight(temp);
327         }
328
329         if (bounds.bottom() > bounds.top())
330         {
331                 double temp = bounds.bottom();
332                 bounds.setBottom(bounds.top());
333                 bounds.setTop(temp);
334         }
335
336         // If the end of the arc is before the beginning, add 360 degrees to it
337         if (end < start)
338                 end += 2.0 * PI;
339
340         // Adjust the bounds depending on which axes are crossed
341         if ((start < PI_OVER_2) && (end > PI_OVER_2))
342                 bounds.setTop(1.0);
343
344         if ((start < PI) && (end > PI))
345                 bounds.setLeft(-1.0);
346
347         if ((start < (PI + PI_OVER_2)) && (end > (PI + PI_OVER_2)))
348                 bounds.setBottom(-1.0);
349
350         if ((start < (2.0 * PI)) && (end > (2.0 * PI)))
351                 bounds.setRight(1.0);
352
353         if ((start < ((2.0 * PI) + PI_OVER_2)) && (end > ((2.0 * PI) + PI_OVER_2)))
354                 bounds.setTop(1.0);
355
356         if ((start < (3.0 * PI)) && (end > (3.0 * PI)))
357                 bounds.setLeft(-1.0);
358
359         if ((start < ((3.0 * PI) + PI_OVER_2)) && (end > ((3.0 * PI) + PI_OVER_2)))
360                 bounds.setBottom(-1.0);
361
362         bounds.setTopLeft(QPointF(bounds.left() * radius, bounds.top() * radius));
363         bounds.setBottomRight(QPointF(bounds.right() * radius, bounds.bottom() * radius));
364         bounds.translate(position.x, position.y);
365         return bounds;
366 }
367
368
369 #if 0
370 /*virtual*/ bool Arc::NeedsUpdate(void)
371 {
372         return needUpdate;
373 }
374 #endif
375
376
377 /*
378 start = 350, span = 20, end = 10, angle = 5
379 angle < start, so angle = 365
380 */
381 bool Arc::AngleInArcSpan(double angle)
382 {
383         // This should be simple except for a small complication: The start angle plus
384         // the angle span can end up being less than the start angle! So, to check
385         // for this possibility, we check to see if the angle passed in is less than
386         // the start angle and if so, add 2PI to the angle passed in. Then we take the
387         // difference between our start angle and this adjusted angle and see if it
388         // falls within our span.
389         if (angle < startAngle)
390                 angle += 2.0 * PI;
391
392         double passedInSpan = angle - startAngle;
393
394         return (passedInSpan <= angleSpan ?  true : false);
395 }
396
397
398 /*virtual*/ void Arc::Enumerate(FILE * file)
399 {
400         fprintf(file, "ARC (%lf,%lf) %lf, %lf, %lf\n", position.x, position.y, radius, startAngle, angleSpan);
401 }
402