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 L. 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"
21 Arc::Arc(Vector p1, double r, double a1, double a2, Object * p/*= NULL*/): Object(p1, p),
22 radius(r), startAngle(a1), angleSpan(a2)
30 /*virtual*/ void Arc::Draw(QPainter * painter)
32 if (state == OSSelected)
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;
39 if ((hitHandle2 || hitHandle3) && objectWasDragged)
43 // If we rotating, we draw a guideline showing the angle we're
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);
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);
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);
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);
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)
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;
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();
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);
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);
96 text = QObject::tr("Radius: %1\nScale: %2%");
97 text = text.arg(radius, 0, 'd', 4).arg(radius / oldRadius * 100.0, 0, 'd', 0);
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);
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);
112 // painter->setPen(QPen(Qt::red, 2.0, Qt::DotLine));
115 painter->setPen(QPen(Qt::black, 1.0, Qt::SolidLine));
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);
124 /*virtual*/ Vector Arc::Center(void)
130 We need at least *four* handles for this object:
134 - one for setting the span of the arc
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?"
140 Also: should put the snap logic into the Object base class (as a static method)...
143 /*virtual*/ bool Arc::Collided(Vector point)
145 objectWasDragged = false;
146 Vector v1 = point - position; // Head minus tail (vector points at "point")
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
155 the center 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).
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();
170 if (v1.Magnitude() < 10.0)
173 else if (Vector(handle3 - point).Magnitude() < 10.0)
176 else if (Vector(handle2 - point).Magnitude() < 10.0)
178 // Resize handle (the arc itself)
179 else if ((v1.Magnitude() < radius + 3.0) && (v1.Magnitude() > radius - 3.0)
180 && AngleInArcSpan(pointerAngle))
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.
191 If I, go to S. Can drag, but only handles 2, 3, & 4 (since the center is not highlighted
193 However, if dragging, revert to I once finished.
195 If S, stay in S. Can drag all.
197 So, we know this. If wasDragged is true, then there's a chance we need to revert to I.
200 So. We have a matrix that looks like this:
214 so let's do like this:
216 if (hitHandle1 || hitHandle2 || hitHandle3 || hitHandle4)
222 oldAngle = startAngle;
232 /*virtual*/ void Arc::PointerMoved(Vector point)
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
238 objectWasDragged = true;
241 if (!(hitHandle1 || hitHandle2 || hitHandle3 || hitHandle4))
244 Vector delta = point - oldPoint;
246 if (hitHandle1) // Move arc
250 else if (hitHandle2) // Rotate arc
252 startAngle = Vector(point - position).Angle();
254 else if (hitHandle3) // Set arc span
256 double angle = Vector(point - position).Angle();
258 if (angle < startAngle)
261 angleSpan = angle - startAngle;
263 else if (hitHandle4) // Resize the radius of the arc
265 radius = Vector(point - position).Magnitude();
272 /*virtual*/ void Arc::PointerReleased(void)
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
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
283 I mean, we could write like:
284 if (objectWasDragged && oldState == OSInactive)
287 but this is actually more compact and cleaner.
289 if (objectWasDragged)
294 /*virtual*/ bool Arc::NeedsUpdate(void)
301 start = 350, span = 20, end = 10, angle = 5
302 angle < start, so angle = 365
304 bool Arc::AngleInArcSpan(double angle)
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)
315 double passedInSpan = angle - startAngle;
317 return (passedInSpan <= angleSpan ? true : false);