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