3 // Part of the Architektonas Project
4 // (C) 2011 Underground Software
5 // See the README and GPLv3 files for licensing and warranty information
7 // JLH = James Hammons <jlhamm@acm.org>
10 // --- ---------- ------------------------------------------------------------
11 // JLH 03/30/2011 Created this file
12 // JLH 04/03/2011 Added information panel (angles) rendering
18 #include "mathconstants.h"
22 Arc::Arc(Vector p1, double r, double a1, double a2, Object * p/*= NULL*/): Object(p1, p),
23 radius(r), startAngle(a1), angleSpan(a2)
31 /*virtual*/ void Arc::Draw(Painter * painter)
35 if (state == OSSelected)
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;
42 if ((hitHandle2 || hitHandle3) && objectWasDragged)
46 // If we rotating, we draw a guideline showing the angle we're
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);
52 painter->DrawLine((int)position.x, (int)position.y, (int)oldLine.x, (int)oldLine.y);
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);
59 painter->DrawLine((int)position.x, (int)position.y, (int)oldPoint.x, (int)oldPoint.y);
62 // Draw the center point of the arc
63 painter->SetPen(QPen(Qt::red, 2.0, Qt::DotLine));
64 painter->DrawHandle(position);
66 // Draw the rotation & span setting handles
67 painter->DrawHandle(handle2);
68 painter->DrawHandle(handle3);
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)
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;
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);
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);
94 text = QObject::tr("Radius: %1\nScale: %2%");
95 text = text.arg(radius, 0, 'd', 4).arg(radius / oldRadius * 100.0, 0, 'd', 0);
98 pen = QPen(QColor(0x00, 0xFF, 0x00), 1.0, Qt::SolidLine);
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);
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);
113 pen = QPen(Qt::black, 1.0, Qt::SolidLine);
114 painter->SetPen(pen);
117 painter->DrawArc(position, radius, startAngle, angleSpan);
120 /*virtual*/ Vector Arc::Center(void)
126 We need at least *four* handles for this object:
130 - one for setting the span of the arc
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?"
136 Also: should put the snap logic into the Object base class (as a static method)...
139 /*virtual*/ bool Arc::Collided(Vector point)
141 objectWasDragged = false;
142 Vector v1 = point - position; // Head minus tail (vector points at "point")
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
151 the center 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).
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();
166 if (v1.Magnitude() < 10.0)
169 else if (Vector(handle3 - point).Magnitude() < 10.0)
172 else if (Vector(handle2 - point).Magnitude() < 10.0)
174 // Resize handle (the arc itself)
175 else if ((v1.Magnitude() < radius + 3.0) && (v1.Magnitude() > radius - 3.0)
176 && AngleInArcSpan(pointerAngle))
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.
187 If I, go to S. Can drag, but only handles 2, 3, & 4 (since the center is not highlighted
189 However, if dragging, revert to I once finished.
191 If S, stay in S. Can drag all.
193 So, we know this. If wasDragged is true, then there's a chance we need to revert to I.
196 So. We have a matrix that looks like this:
210 so let's do like this:
212 if (hitHandle1 || hitHandle2 || hitHandle3 || hitHandle4)
218 oldAngle = startAngle;
228 /*virtual*/ void Arc::PointerMoved(Vector point)
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
234 objectWasDragged = true;
237 if (!(hitHandle1 || hitHandle2 || hitHandle3 || hitHandle4))
240 Vector delta = point - oldPoint;
242 if (hitHandle1) // Move arc
246 else if (hitHandle2) // Rotate arc
248 startAngle = Vector(point - position).Angle();
250 else if (hitHandle3) // Set arc span
252 double angle = Vector(point - position).Angle();
254 if (angle < startAngle)
257 angleSpan = angle - startAngle;
259 else if (hitHandle4) // Resize the radius of the arc
261 radius = Vector(point - position).Magnitude();
268 /*virtual*/ void Arc::PointerReleased(void)
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
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
279 I mean, we could write like:
280 if (objectWasDragged && oldState == OSInactive)
283 but this is actually more compact and cleaner.
285 if (objectWasDragged)
290 /*virtual*/ bool Arc::NeedsUpdate(void)
297 start = 350, span = 20, end = 10, angle = 5
298 angle < start, so angle = 365
300 bool Arc::AngleInArcSpan(double angle)
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)
311 double passedInSpan = angle - startAngle;
313 return (passedInSpan <= angleSpan ? true : false);