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->DrawEllipse(position, 4.0, 4.0);
66 // Draw the rotation & span setting handles
67 painter->DrawEllipse(handle2, 4.0, 4.0);
68 painter->DrawEllipse(handle3, 4.0, 4.0);
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;
79 //close, but no cigar. we need to "invert" our transformation to make this work properly
80 // return QPoint(-offsetX + x, (size().height() - (-offsetY + y)) * +1.0);
81 // painter->translate(0, viewportHeight);
82 // painter->scale(1.0, -1.0);
83 // Give up for now; just paint the info panel in the upper left corner of the screen
84 // painter->resetTransform();
89 text = QObject::tr("Abs ") + QChar(0x2221) + ": %1" + QChar(0x00B0)
90 + QObject::tr("\nRel ") + QChar(0x2221) + ": %2" + QChar(0x00B0);
91 text = text.arg(absAngle, 0, 'd', 4).arg(relAngle, 0, 'd', 4);
95 text = QObject::tr("Abs ") + QChar(0x2221) + ": %1" + QChar(0x00B0)
96 + QObject::tr("\nSpan: %2") + QChar(0x00B0);
97 text = text.arg(absAngle, 0, 'd', 4).arg(angleSpan * RADIANS_TO_DEGREES, 0, 'd', 4);
101 text = QObject::tr("Radius: %1\nScale: %2%");
102 text = text.arg(radius, 0, 'd', 4).arg(radius / oldRadius * 100.0, 0, 'd', 0);
105 pen = QPen(QColor(0x00, 0xFF, 0x00), 1.0, Qt::SolidLine);
106 painter->SetPen(pen);
107 painter->SetBrush(QBrush(QColor(0x40, 0xFF, 0x40, 0x9F)));
108 QRectF textRect(10.0, 10.0, 220.0, 60.0); // x, y, w, h
109 painter->DrawRoundedRect(textRect, 7.0, 7.0);
111 textRect.setLeft(textRect.left() + 14);
112 painter->SetFont(*Object::font);
113 pen = QPen(QColor(0xDF, 0x5F, 0x00), 1.0, Qt::SolidLine);
114 painter->SetPen(pen);
115 painter->DrawText(textRect, Qt::AlignVCenter, text);
116 // painter->Restore();
119 // painter->setPen(QPen(Qt::red, 2.0, Qt::DotLine));
123 pen = QPen(Qt::black, 1.0, Qt::SolidLine);
124 painter->SetPen(pen);
128 QRectF rectangle(QPointF(position.x - radius, position.y - radius),
129 QPointF(position.x + radius, position.y + radius));
130 int angle1 = (int)(startAngle * RADIANS_TO_DEGREES * 16.0);
131 int angle2 = (int)(angleSpan * RADIANS_TO_DEGREES * 16.0);
132 painter->DrawArc(rectangle, -angle1, -angle2);
134 painter->DrawArc(position, radius, startAngle, angleSpan);
138 /*virtual*/ Vector Arc::Center(void)
144 We need at least *four* handles for this object:
148 - one for setting the span of the arc
150 We need to think about the intuitive way (if there is any) to grab and
151 manipulate a complex object like this... Need to think, "What should happen when
152 I click here and drag there?"
154 Also: should put the snap logic into the Object base class (as a static method)...
157 /*virtual*/ bool Arc::Collided(Vector point)
159 objectWasDragged = false;
160 Vector v1 = point - position; // Head minus tail (vector points at "point")
162 // Check for collision with various things...
163 hitHandle1 = false; // Moving
164 hitHandle2 = false; // Rotation
165 hitHandle3 = false; // Setting span of the arc
166 hitHandle4 = false; // Resizing
169 the center of the arc
172 The point on a unit circle given an angle a is x = cos(a), y = sin(a)
173 This vector is already unitized, so all we need to do to get our point is to multiply it by
174 radius (to get the length correct) and add it to the center point (to get the correct position).
176 Point p1(cos(startAngle), sin(startAngle));
177 Point p2(cos(startAngle + angleSpan), sin(startAngle + angleSpan));
178 Vector handle2 = (p1 * radius) + position;
179 Vector handle3 = (p2 * radius) + position;
180 double pointerAngle = v1.Angle();
184 if (v1.Magnitude() < 10.0)
187 else if (Vector(handle3 - point).Magnitude() < 10.0)
190 else if (Vector(handle2 - point).Magnitude() < 10.0)
192 // Resize handle (the arc itself)
193 else if ((v1.Magnitude() < radius + 3.0) && (v1.Magnitude() > radius - 3.0)
194 && AngleInArcSpan(pointerAngle))
200 We want the arc to go into OSSelected mode if we click on it but don't drag.
201 If we don't click anywhere on the arc, then we want it to go into OSInactive mode.
202 Otherwise, we hit a handle and we want to modify the object whether it's in
203 OSSelected mode or not.
205 If I, go to S. Can drag, but only handles 2, 3, & 4 (since the center is not highlighted
207 However, if dragging, revert to I once finished.
209 If S, stay in S. Can drag all.
211 So, we know this. If wasDragged is true, then there's a chance we need to revert to I.
214 So. We have a matrix that looks like this:
228 so let's do like this:
230 if (hitHandle1 || hitHandle2 || hitHandle3 || hitHandle4)
236 oldAngle = startAngle;
246 /*virtual*/ void Arc::PointerMoved(Vector point)
248 // The TLC will send these messages if the object is selected but not clicked on.
249 // So we have to be careful with our assumptions here.
250 // This is actually untrue in that case, we need to come up with something better
252 objectWasDragged = true;
255 if (!(hitHandle1 || hitHandle2 || hitHandle3 || hitHandle4))
258 Vector delta = point - oldPoint;
260 if (hitHandle1) // Move arc
264 else if (hitHandle2) // Rotate arc
266 startAngle = Vector(point - position).Angle();
268 else if (hitHandle3) // Set arc span
270 double angle = Vector(point - position).Angle();
272 if (angle < startAngle)
275 angleSpan = angle - startAngle;
277 else if (hitHandle4) // Resize the radius of the arc
279 radius = Vector(point - position).Magnitude();
286 /*virtual*/ void Arc::PointerReleased(void)
288 hitHandle1 = hitHandle2 = hitHandle3 = hitHandle4 = false;
289 // Here we check for just a click: If object was clicked and dragged, then
290 // revert to the old state (OSInactive). Otherwise, keep the new state that
292 /*Maybe it would be better to just check for "object was dragged" state and not have to worry
293 about keeping track of old states...
294 Well, we can't know if it was dragged from Inactive or not, that's the problem. We could make
295 a variable called "needToRevertToInactive" instead
297 I mean, we could write like:
298 if (objectWasDragged && oldState == OSInactive)
301 but this is actually more compact and cleaner.
303 if (objectWasDragged)
308 /*virtual*/ bool Arc::NeedsUpdate(void)
315 start = 350, span = 20, end = 10, angle = 5
316 angle < start, so angle = 365
318 bool Arc::AngleInArcSpan(double angle)
320 // This should be simple except for a small complication: The start angle plus
321 // the angle span can end up being less than the start angle! So, to check
322 // for this possibility, we check to see if the angle passed in is less than
323 // the start angle and if so, add 2PI to the angle passed in. Then we take the
324 // difference between our start angle and this adjusted angle and see if it
325 // falls within our span.
326 if (angle < startAngle)
329 double passedInSpan = angle - startAngle;
331 return (passedInSpan <= angleSpan ? true : false);