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*/):
23 Object(p1, p), /*type(OTArc),*/ radius(r), startAngle(a1), angleSpan(a2)
25 // This is in the base class, why can't we use the contructor to fill it???
35 /*virtual*/ void Arc::Draw(Painter * painter)
39 if (state == OSSelected)
41 Point p1(cos(startAngle), sin(startAngle));
42 Point p2(cos(startAngle + angleSpan), sin(startAngle + angleSpan));
43 Vector handle2 = (p1 * radius) + position;
44 Vector handle3 = (p2 * radius) + position;
46 if ((hitHandle2 || hitHandle3) && objectWasDragged)
50 // If we rotating, we draw a guideline showing the angle we're
52 Point p3(cos(oldAngle), sin(oldAngle));
53 Vector oldLine = (p3 * (radius * 1.25)) + position;
54 pen = QPen(QColor(0x80, 0x80, 0x80), 1.0, Qt::DashLine);
56 painter->DrawLine((int)position.x, (int)position.y, (int)oldLine.x, (int)oldLine.y);
59 // In rotating and setting the span, we draw a line showing where
60 // we angle/span is that we're setting.
61 pen = QPen(QColor(0x00, 0xC0, 0x80), 1.0, Qt::DashLine);
63 painter->DrawLine((int)position.x, (int)position.y, (int)oldPoint.x, (int)oldPoint.y);
66 // Draw the center point of the arc
67 painter->SetPen(QPen(Qt::red, 2.0, Qt::DotLine));
68 painter->DrawHandle(position);
70 // Draw the rotation & span setting handles
71 painter->DrawHandle(handle2);
72 painter->DrawHandle(handle3);
74 // If we're rotating or setting the span, draw an information panel
75 // showing both absolute and relative angles being set.
76 if ((hitHandle2 || hitHandle3 || hitHandle4) && objectWasDragged)
78 double absAngle = (Vector(oldPoint - position).Angle()) * RADIANS_TO_DEGREES;
79 double relAngle = (startAngle >= oldAngle ? startAngle - oldAngle :
80 startAngle - oldAngle + (2.0 * PI)) * RADIANS_TO_DEGREES;
86 text = QObject::tr("Abs ") + QChar(0x2221) + ": %1" + QChar(0x00B0)
87 + QObject::tr("\nRel ") + QChar(0x2221) + ": %2" + QChar(0x00B0);
88 text = text.arg(absAngle, 0, 'd', 4).arg(relAngle, 0, 'd', 4);
92 text = QObject::tr("Abs ") + QChar(0x2221) + ": %1" + QChar(0x00B0)
93 + QObject::tr("\nSpan: %2") + QChar(0x00B0);
94 text = text.arg(absAngle, 0, 'd', 4).arg(angleSpan * RADIANS_TO_DEGREES, 0, 'd', 4);
98 text = QObject::tr("Radius: %1\nScale: %2%");
99 text = text.arg(radius, 0, 'd', 4).arg(radius / oldRadius * 100.0, 0, 'd', 0);
102 pen = QPen(QColor(0x00, 0xFF, 0x00), 1.0, Qt::SolidLine);
103 painter->SetPen(pen);
104 painter->SetBrush(QBrush(QColor(0x40, 0xFF, 0x40, 0x9F)));
105 QRectF textRect(10.0, 10.0, 270.0, 70.0); // x, y, w, h
106 painter->DrawRoundedRect(textRect, 7.0, 7.0);
108 textRect.setLeft(textRect.left() + 14);
109 painter->SetFont(*Object::font);
110 // pen = QPen(QColor(0xDF, 0x5F, 0x00), 1.0, Qt::SolidLine);
111 pen = QPen(QColor(0x00, 0x5F, 0xDF));
112 painter->SetPen(pen);
113 painter->DrawText(textRect, Qt::AlignVCenter, text);
114 painter->SetPen(QPen(QColor(0xDF, 0x5F, 0x00)));
119 pen = QPen(Qt::black, 1.0, Qt::SolidLine);
120 painter->SetPen(pen);
123 painter->DrawArc(position, radius, startAngle, angleSpan);
124 // painter->DrawRect(Extents());
128 /*virtual*/ Vector Arc::Center(void)
135 We need at least *four* handles for this object:
139 - one for setting the span of the arc
141 We need to think about the intuitive way (if there is any) to grab and
142 manipulate a complex object like this... Need to think, "What should happen when
143 I click here and drag there?"
145 Also: should put the snap logic into the Object base class (as a static method)...
148 /*virtual*/ bool Arc::Collided(Vector point)
150 objectWasDragged = false;
151 Vector v1 = point - position; // Head minus tail (vector points at "point")
153 // Check for collision with various things...
154 hitHandle1 = false; // Moving
155 hitHandle2 = false; // Rotation
156 hitHandle3 = false; // Setting span of the arc
157 hitHandle4 = false; // Resizing
160 the center of the arc
163 The point on a unit circle given an angle a is x = cos(a), y = sin(a)
164 This vector is already unitized, so all we need to do to get our point is to multiply it by
165 radius (to get the length correct) and add it to the center point (to get the correct position).
167 Point p1(cos(startAngle), sin(startAngle));
168 Point p2(cos(startAngle + angleSpan), sin(startAngle + angleSpan));
169 Vector handle2 = (p1 * radius) + position;
170 Vector handle3 = (p2 * radius) + position;
171 double pointerAngle = v1.Angle();
175 if (v1.Magnitude() < 10.0)
178 else if (Vector(handle3 - point).Magnitude() < 10.0)
181 else if (Vector(handle2 - point).Magnitude() < 10.0)
183 // Resize handle (the arc itself)
184 else if ((v1.Magnitude() < radius + 3.0) && (v1.Magnitude() > radius - 3.0)
185 && AngleInArcSpan(pointerAngle))
191 We want the arc to go into OSSelected mode if we click on it but don't drag.
192 If we don't click anywhere on the arc, then we want it to go into OSInactive mode.
193 Otherwise, we hit a handle and we want to modify the object whether it's in
194 OSSelected mode or not.
196 If I, go to S. Can drag, but only handles 2, 3, & 4 (since the center is not highlighted
198 However, if dragging, revert to I once finished.
200 If S, stay in S. Can drag all.
202 So, we know this. If wasDragged is true, then there's a chance we need to revert to I.
205 So. We have a matrix that looks like this:
219 so let's do like this:
221 if (hitHandle1 || hitHandle2 || hitHandle3 || hitHandle4)
227 oldAngle = startAngle;
238 /*virtual*/ void Arc::PointerMoved(Vector point)
240 if (selectionInProgress)
242 // Check for whether or not the rect contains this circle
243 if (selection.normalized().contains(Extents()))
251 // The TLC will send these messages if the object is selected but not clicked on.
252 // So we have to be careful with our assumptions here.
253 // This is actually untrue in that case, we need to come up with something better
255 objectWasDragged = true;
258 if (!(hitHandle1 || hitHandle2 || hitHandle3 || hitHandle4))
261 Vector delta = point - oldPoint;
263 if (hitHandle1) // Move arc
267 else if (hitHandle2) // Rotate arc
269 startAngle = Vector(point - position).Angle();
271 else if (hitHandle3) // Set arc span
273 double angle = Vector(point - position).Angle();
275 if (angle < startAngle)
278 angleSpan = angle - startAngle;
280 else if (hitHandle4) // Resize the radius of the arc
282 radius = Vector(point - position).Magnitude();
290 /*virtual*/ void Arc::PointerReleased(void)
292 hitHandle1 = hitHandle2 = hitHandle3 = hitHandle4 = false;
293 // Here we check for just a click: If object was clicked and dragged, then
294 // revert to the old state (OSInactive). Otherwise, keep the new state that
297 Maybe it would be better to just check for "object was dragged" state and not
298 have to worry about keeping track of old states...
299 Well, we can't know if it was dragged from Inactive or not, that's the problem.
300 We could make a variable called "needToRevertToInactive" instead
302 I mean, we could write like:
303 if (objectWasDragged && oldState == OSInactive)
306 but this is actually more compact and cleaner.
308 if (objectWasDragged)
313 /*virtual*/ QRectF Arc::Extents(void)
315 double start = startAngle;
316 double end = start + angleSpan;
317 QPointF p1(cos(start), sin(start));
318 QPointF p2(cos(end), sin(end));
319 QRectF bounds(p1, p2);
321 // Swap X/Y coordinates if they're backwards...
322 if (bounds.left() > bounds.right())
324 double temp = bounds.left();
325 bounds.setLeft(bounds.right());
326 bounds.setRight(temp);
329 if (bounds.bottom() > bounds.top())
331 double temp = bounds.bottom();
332 bounds.setBottom(bounds.top());
336 // If the end of the arc is before the beginning, add 360 degrees to it
340 // Adjust the bounds depending on which axes are crossed
341 if ((start < PI_OVER_2) && (end > PI_OVER_2))
344 if ((start < PI) && (end > PI))
345 bounds.setLeft(-1.0);
347 if ((start < (PI + PI_OVER_2)) && (end > (PI + PI_OVER_2)))
348 bounds.setBottom(-1.0);
350 if ((start < (2.0 * PI)) && (end > (2.0 * PI)))
351 bounds.setRight(1.0);
353 if ((start < ((2.0 * PI) + PI_OVER_2)) && (end > ((2.0 * PI) + PI_OVER_2)))
356 if ((start < (3.0 * PI)) && (end > (3.0 * PI)))
357 bounds.setLeft(-1.0);
359 if ((start < ((3.0 * PI) + PI_OVER_2)) && (end > ((3.0 * PI) + PI_OVER_2)))
360 bounds.setBottom(-1.0);
362 bounds.setTopLeft(QPointF(bounds.left() * radius, bounds.top() * radius));
363 bounds.setBottomRight(QPointF(bounds.right() * radius, bounds.bottom() * radius));
364 bounds.translate(position.x, position.y);
370 /*virtual*/ bool Arc::NeedsUpdate(void)
378 start = 350, span = 20, end = 10, angle = 5
379 angle < start, so angle = 365
381 bool Arc::AngleInArcSpan(double angle)
383 // This should be simple except for a small complication: The start angle plus
384 // the angle span can end up being less than the start angle! So, to check
385 // for this possibility, we check to see if the angle passed in is less than
386 // the start angle and if so, add 2PI to the angle passed in. Then we take the
387 // difference between our start angle and this adjusted angle and see if it
388 // falls within our span.
389 if (angle < startAngle)
392 double passedInSpan = angle - startAngle;
394 return (passedInSpan <= angleSpan ? true : false);
398 /*virtual*/ void Arc::Enumerate(FILE * file)
400 fprintf(file, "ARC (%lf,%lf) %lf, %lf, %lf\n", position.x, position.y, radius, startAngle, angleSpan);
404 /*virtual*/ Object * Arc::Copy(void)
406 #warning "!!! This doesn't take care of attached Dimensions !!!"
408 This is a real problem. While having a pointer in the Dimension to this line's points
409 is fast & easy, it creates a huge problem when trying to replicate an object like this.
411 Maybe a way to fix that then, is to have reference numbers instead of pointers. That
412 way, if you copy them, ... you might still have problems. Because you can't be sure if
413 a copy will be persistant or not, you then *definitely* do not want them to have the
414 same reference number.
416 return new Arc(position, radius, startAngle, angleSpan, parent);