]> Shamusworld >> Repos - architektonas/blob - src/arc.cpp
8fc66319bbe540ef96a153ed193c6ec4635993e9
[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 }
125
126
127 /*virtual*/ Vector Arc::Center(void)
128 {
129         return position;
130 }
131
132
133 /*
134  We need at least *four* handles for this object:
135  - one for moving
136  - one for resizing
137  - one for rotation
138  - one for setting the span of the arc
139
140 We need to think about the intuitive way (if there is any) to grab and
141 manipulate a complex object like this... Need to think, "What should happen when
142 I click here and drag there?"
143
144 Also: should put the snap logic into the Object base class (as a static method)...
145 */
146
147 /*virtual*/ bool Arc::Collided(Vector point)
148 {
149         objectWasDragged = false;
150         Vector v1 = point - position;                   // Head minus tail (vector points at "point")
151
152         // Check for collision with various things...
153         hitHandle1 = false;     // Moving
154         hitHandle2 = false;     // Rotation
155         hitHandle3 = false;     // Setting span of the arc
156         hitHandle4 = false;     // Resizing
157 /*
158 What we have:
159 the center of the arc
160 the starting angle
161 the span of the arc
162 The point on a unit circle given an angle a is x = cos(a), y = sin(a)
163 This vector is already unitized, so all we need to do to get our point is to multiply it by
164 radius (to get the length correct) and add it to the center point (to get the correct position).
165 */
166         Point p1(cos(startAngle), sin(startAngle));
167         Point p2(cos(startAngle + angleSpan), sin(startAngle + angleSpan));
168         Vector handle2 = (p1 * radius) + position;
169         Vector handle3 = (p2 * radius) + position;
170         double pointerAngle = v1.Angle();
171
172 #if 1
173         // Center handle
174         if (v1.Magnitude() < 10.0)
175                 hitHandle1 = true;
176         // Span handle
177         else if (Vector(handle3 - point).Magnitude() < 10.0)
178                 hitHandle3 = true;
179         // Rotate handle
180         else if (Vector(handle2 - point).Magnitude() < 10.0)
181                 hitHandle2 = true;
182         // Resize handle (the arc itself)
183         else if ((v1.Magnitude() < radius + 3.0) && (v1.Magnitude() > radius - 3.0)
184                 && AngleInArcSpan(pointerAngle))
185                 hitHandle4 = true;
186 #endif
187
188 /*
189 State Management:
190 We want the arc to go into OSSelected mode if we click on it but don't drag.
191 If we don't click anywhere on the arc, then we want it to go into OSInactive mode.
192 Otherwise, we hit a handle and we want to modify the object whether it's in
193 OSSelected mode or not.
194
195 If I, go to S. Can drag, but only handles 2, 3, & 4 (since the center is not highlighted
196 until we select it.
197         However, if dragging, revert to I once finished.
198
199 If S, stay in S. Can drag all.
200
201 So, we know this. If wasDragged is true, then there's a chance we need to revert to I.
202 How can we be sure?
203
204 So. We have a matrix that looks like this:
205
206         |Was I|Was S
207 --------+-----+-----
208 Inactive|     |   
209 --------+-----+-----
210 Selected|     |   
211
212         |h1|h2|h3|h4
213 --------+--+--+--+--
214 Inactive|  |  |  |
215 --------+--+--+--+--
216 Selected|  |  |  |
217
218 so let's do like this:
219 */
220         if (hitHandle1 || hitHandle2 || hitHandle3 || hitHandle4)
221         {
222                 oldState = state;
223                 state = OSSelected;
224 //              oldPoint = point;
225                 oldPoint = position;
226                 oldAngle = startAngle;
227                 oldRadius = radius;
228
229                 return true;
230         }
231
232         state = OSInactive;
233         return false;
234 }
235
236
237 /*virtual*/ void Arc::PointerMoved(Vector point)
238 {
239         // The TLC will send these messages if the object is selected but not clicked on.
240         // So we have to be careful with our assumptions here.
241         // This is actually untrue in that case, we need to come up with something better
242         // here...
243         objectWasDragged = true;
244         needUpdate = false;
245
246         if (!(hitHandle1 || hitHandle2 || hitHandle3 || hitHandle4))
247                 return;
248
249         Vector delta = point - oldPoint;
250
251         if (hitHandle1)                 // Move arc
252         {
253                 position += delta;
254         }
255         else if (hitHandle2)    // Rotate arc
256         {
257                 startAngle = Vector(point - position).Angle();
258         }
259         else if (hitHandle3)    // Set arc span
260         {
261                 double angle = Vector(point - position).Angle();
262
263                 if (angle < startAngle)
264                         angle += 2.0 * PI;
265
266                 angleSpan = angle - startAngle;
267         }
268         else if (hitHandle4)    // Resize the radius of the arc
269         {
270                 radius = Vector(point - position).Magnitude();
271         }
272
273         oldPoint = point;
274         needUpdate = true;
275 }
276
277
278 /*virtual*/ void Arc::PointerReleased(void)
279 {
280         hitHandle1 = hitHandle2 = hitHandle3 = hitHandle4 = false;
281         // Here we check for just a click: If object was clicked and dragged, then
282         // revert to the old state (OSInactive). Otherwise, keep the new state that
283         // we set.
284 /*Maybe it would be better to just check for "object was dragged" state and not have to worry
285 about keeping track of old states...
286 Well, we can't know if it was dragged from Inactive or not, that's the problem. We could make
287 a variable called "needToRevertToInactive" instead
288
289 I mean, we could write like:
290         if (objectWasDragged && oldState == OSInactive)
291                 state = OSInactive;
292
293 but this is actually more compact and cleaner.
294 */
295         if (objectWasDragged)
296                 state = oldState;
297 }
298
299
300 /*virtual*/ QRectF Arc::Extents(void)
301 {
302 #warning "!!! Arc extents not calculated !!!"
303         return QRectF();
304 }
305
306
307 #if 0
308 /*virtual*/ bool Arc::NeedsUpdate(void)
309 {
310         return needUpdate;
311 }
312 #endif
313
314
315 #if 0
316 /*virtual*/ ObjectType Arc::Type(void)
317 {
318         return OTArc;
319 }
320 #endif
321
322
323 /*
324 start = 350, span = 20, end = 10, angle = 5
325 angle < start, so angle = 365
326 */
327 bool Arc::AngleInArcSpan(double angle)
328 {
329         // This should be simple except for a small complication: The start angle plus
330         // the angle span can end up being less than the start angle! So, to check
331         // for this possibility, we check to see if the angle passed in is less than
332         // the start angle and if so, add 2PI to the angle passed in. Then we take the
333         // difference between our start angle and this adjusted angle and see if it
334         // falls within our span.
335         if (angle < startAngle)
336                 angle += 2.0 * PI;
337
338         double passedInSpan = angle - startAngle;
339
340         return (passedInSpan <= angleSpan ?  true : false);
341 }
342
343
344 /*virtual*/ void Arc::Enumerate(FILE * file)
345 {
346         fprintf(file, "ARC (%lf,%lf) %lf, %lf, %lf\n", position.x, position.y, radius, startAngle, angleSpan);
347 }
348