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