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
13 // JLH 08/16/2013 Added continuous user feedack like for Line and Circle
19 #include "mathconstants.h"
23 Arc::Arc(Vector p1, double r, double a1, double a2, Object * p/*= NULL*/):
24 Object(p1, p), /*type(OTArc),*/ radius(r), startAngle(a1), angleSpan(a2),
25 draggingCenter(false), draggingEdge(false), draggingRotate(false),
27 hitCenter(false), hitArc(false), hitRotate(false), hitSpan(false)
29 // This is in the base class, why can't we use the contructor to fill it???
40 /*virtual*/ void Arc::Draw(Painter * painter)
43 painter->SetPen(QPen(Qt::red, 2.0, Qt::DotLine));
45 Point p1(cos(startAngle), sin(startAngle));
46 Point p2(cos(startAngle + angleSpan), sin(startAngle + angleSpan));
47 Vector handle2 = (p1 * radius) + position;
48 Vector handle3 = (p2 * radius) + position;
50 if ((state == OSSelected) || ((state == OSInactive) && hitRotate))
51 painter->DrawHandle(handle2);
53 if ((state == OSSelected) || ((state == OSInactive) && hitSpan))
54 painter->DrawHandle(handle3);
56 if ((state == OSSelected) || ((state == OSInactive) && hitCenter))
57 painter->DrawHandle(position);
59 if ((state == OSInactive) && !hitArc)
60 painter->SetPen(QPen(Qt::black, 1.0, Qt::SolidLine));
62 painter->DrawArc(position, radius, startAngle, angleSpan);
64 if (draggingRotate || draggingSpan)
68 // If we rotating, we draw a guideline showing the angle we're
70 Point p3(cos(oldAngle), sin(oldAngle));
71 Vector oldLine = (p3 * (radius * 1.25)) + position;
72 pen = QPen(QColor(0x80, 0x80, 0x80), 1.0, Qt::DashLine);
74 // painter->DrawLine((int)position.x, (int)position.y, (int)oldLine.x, (int)oldLine.y);
75 painter->DrawLine(position, oldLine);
78 // In rotating and setting the span, we draw a line showing where
79 // we angle/span is that we're setting.
80 pen = QPen(QColor(0x00, 0xC0, 0x80), 1.0, Qt::DashLine);
82 painter->DrawLine((int)position.x, (int)position.y, (int)oldPoint.x, (int)oldPoint.y);
85 // If we're rotating or setting the span, draw an information panel
86 // showing both absolute and relative angles being set.
87 if (draggingRotate || draggingSpan || draggingEdge)
89 double absAngle = (Vector(oldPoint - position).Angle()) * RADIANS_TO_DEGREES;
90 double relAngle = (startAngle >= oldAngle ? startAngle - oldAngle :
91 startAngle - oldAngle + (2.0 * PI)) * RADIANS_TO_DEGREES;
97 text = QObject::tr("Abs ") + QChar(0x2221) + ": %1" + QChar(0x00B0)
98 + QObject::tr("\nRel ") + QChar(0x2221) + ": %2" + QChar(0x00B0);
99 text = text.arg(absAngle, 0, 'd', 4).arg(relAngle, 0, 'd', 4);
101 else if (draggingSpan)
103 text = QObject::tr("Abs ") + QChar(0x2221) + ": %1" + QChar(0x00B0)
104 + QObject::tr("\nSpan: %2") + QChar(0x00B0);
105 text = text.arg(absAngle, 0, 'd', 4).arg(angleSpan * RADIANS_TO_DEGREES, 0, 'd', 4);
107 else if (draggingEdge)
109 text = QObject::tr("Radius: %1\nScale: %2%");
110 text = text.arg(radius, 0, 'd', 4).arg(radius / oldRadius * 100.0, 0, 'd', 0);
114 pen = QPen(QColor(0x00, 0xFF, 0x00), 1.0, Qt::SolidLine);
115 painter->SetPen(pen);
116 painter->SetBrush(QBrush(QColor(0x40, 0xFF, 0x40, 0x9F)));
117 QRectF textRect(10.0, 10.0, 270.0, 70.0); // x, y, w, h
118 painter->DrawRoundedRect(textRect, 7.0, 7.0);
120 textRect.setLeft(textRect.left() + 14);
121 painter->SetFont(*Object::font);
122 pen = QPen(QColor(0x00, 0x5F, 0xDF));
123 painter->SetPen(pen);
124 painter->DrawText(textRect, Qt::AlignVCenter, text);
126 painter->DrawInformativeText(text);
132 /*virtual*/ Vector Arc::Center(void)
139 We need at least *four* handles for this object:
143 - one for setting the span of the arc
145 We need to think about the intuitive way (if there is any) to grab and
146 manipulate a complex object like this... Need to think, "What should happen when
147 I click here and drag there?"
149 Also: should put the snap logic into the Object base class (as a static method)...
152 /*virtual*/ bool Arc::Collided(Vector point)
154 objectWasDragged = false;
155 bool hitSomething = HitTest(point);
156 draggingCenter = hitCenter;
157 draggingEdge = hitArc;
158 draggingRotate = hitRotate;
159 draggingSpan = hitSpan;
161 // Now that we've done our hit testing on the non-snapped point, snap it if
164 point = SnapPointToGrid(point);
168 We want the arc to go into OSSelected mode if we click on it but don't drag.
169 If we don't click anywhere on the arc, then we want it to go into OSInactive mode.
170 Otherwise, we hit a handle and we want to modify the object whether it's in
171 OSSelected mode or not.
173 If I, go to S. Can drag, but only handles 2, 3, & 4 (since the center is not highlighted
175 However, if dragging, revert to I once finished.
177 If S, stay in S. Can drag all.
179 So, we know this. If wasDragged is true, then there's a chance we need to revert to I.
182 So. We have a matrix that looks like this:
196 so let's do like this:
204 oldAngle = startAngle;
214 /*virtual*/ void Arc::PointerMoved(Vector point)
216 // one other thing to check here for is if a modifier key is being held as well,
217 // to allow for multi-selection
218 if (selectionInProgress)
220 // Check for whether or not the rect contains this circle
221 // if (selection.normalized().contains(Extents()))
222 if (selection.contains(Extents()))
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;
235 // needUpdate = false;
238 needUpdate = HitStateChanged();
239 objectWasDragged = (draggingCenter | draggingEdge | draggingRotate | draggingSpan);
241 if (objectWasDragged)
243 // if (!(hitHandle1 || hitHandle2 || hitHandle3 || hitHandle4))
246 // Vector delta = point - oldPoint;
250 else if (draggingEdge)
251 radius = Vector::Magnitude(point, position);
252 else if (draggingRotate)
254 startAngle = Vector(point - position).Angle();
256 else if (draggingSpan)
258 double angle = Vector(point - position).Angle();
260 if (angle < startAngle)
263 angleSpan = angle - startAngle;
266 // Why save this? For rendering code?
268 // needUpdate = true;
272 /*virtual*/ void Arc::PointerReleased(void)
274 // Mouse went up, so our dragging is done (if any *was* done, that is)
275 // hitHandle1 = hitHandle2 = hitHandle3 = hitHandle4 = false;
276 draggingCenter = draggingEdge = draggingRotate = draggingSpan = false;
277 hitCenter = hitArc = hitRotate = hitSpan = false;
279 // If the object was dragged, then revert to the old state.
280 // Otherwise, we were probably just clicked, and want to stay in the selected state.
281 if (objectWasDragged)
286 /*virtual*/ bool Arc::HitTest(Point point)
288 hitCenter = hitArc = hitRotate = hitSpan = false;
292 the center of the arc
295 The point on a unit circle given an angle a is x = cos(a), y = sin(a)
296 This vector is already unitized, so all we need to do to get our point is to
297 multiply it by radius (to get the length correct) and add it to the center
298 point (to get the correct position).
300 Vector v1(point, position); // Head minus tail (vector points at "point")
301 Point p1(cos(startAngle), sin(startAngle));
302 Point p2(cos(startAngle + angleSpan), sin(startAngle + angleSpan));
303 Vector handle2 = (p1 * radius) + position;
304 Vector handle3 = (p2 * radius) + position;
305 double pointerAngle = v1.Angle();
306 double length = v1.Magnitude();
309 if (v1.Magnitude() < 10.0)
311 else if (Vector(handle3 - point).Magnitude() < 10.0)
313 else if (Vector(handle2 - point).Magnitude() < 10.0)
315 else if ((v1.Magnitude() < radius + 3.0) && (v1.Magnitude() > radius - 3.0)
316 && AngleInArcSpan(pointerAngle))
319 if ((length * Painter::zoom) < 8.0)
321 else if (((fabs(length - radius) * Painter::zoom) < 2.0)
322 && AngleInArcSpan(pointerAngle))
324 else if ((Vector::Magnitude(handle2, point) * Painter::zoom) < 8.0)
326 else if ((Vector::Magnitude(handle3, point) * Painter::zoom) < 8.0)
330 return (hitCenter || hitArc || hitRotate || hitSpan ? true : false);
334 /*virtual*/ QRectF Arc::Extents(void)
336 double start = startAngle;
337 double end = start + angleSpan;
338 QPointF p1(cos(start), sin(start));
339 QPointF p2(cos(end), sin(end));
340 QRectF bounds(p1, p2);
342 // Swap X/Y coordinates if they're backwards...
343 if (bounds.left() > bounds.right())
345 double temp = bounds.left();
346 bounds.setLeft(bounds.right());
347 bounds.setRight(temp);
350 if (bounds.bottom() > bounds.top())
352 double temp = bounds.bottom();
353 bounds.setBottom(bounds.top());
357 // If the end of the arc is before the beginning, add 360 degrees to it
361 // Adjust the bounds depending on which axes are crossed
362 if ((start < PI_OVER_2) && (end > PI_OVER_2))
365 if ((start < PI) && (end > PI))
366 bounds.setLeft(-1.0);
368 if ((start < (PI + PI_OVER_2)) && (end > (PI + PI_OVER_2)))
369 bounds.setBottom(-1.0);
371 if ((start < (2.0 * PI)) && (end > (2.0 * PI)))
372 bounds.setRight(1.0);
374 if ((start < ((2.0 * PI) + PI_OVER_2)) && (end > ((2.0 * PI) + PI_OVER_2)))
377 if ((start < (3.0 * PI)) && (end > (3.0 * PI)))
378 bounds.setLeft(-1.0);
380 if ((start < ((3.0 * PI) + PI_OVER_2)) && (end > ((3.0 * PI) + PI_OVER_2)))
381 bounds.setBottom(-1.0);
383 bounds.setTopLeft(QPointF(bounds.left() * radius, bounds.top() * radius));
384 bounds.setBottomRight(QPointF(bounds.right() * radius, bounds.bottom() * radius));
385 bounds.translate(position.x, position.y);
391 start = 350, span = 20, end = 10, angle = 5
392 angle < start, so angle = 365
394 bool Arc::AngleInArcSpan(double angle)
396 // This should be simple except for a small complication: The start angle plus
397 // the angle span can end up being less than the start angle! So, to check
398 // for this possibility, we check to see if the angle passed in is less than
399 // the start angle and if so, add 2PI to the angle passed in. Then we take the
400 // difference between our start angle and this adjusted angle and see if it
401 // falls within our span.
402 if (angle < startAngle)
405 double passedInSpan = angle - startAngle;
407 return (passedInSpan <= angleSpan ? true : false);
411 void Arc::SaveHitState(void)
413 oldHitCenter = hitCenter;
415 oldHitRotate = hitRotate;
416 oldHitSpan = hitSpan;
420 bool Arc::HitStateChanged(void)
422 if ((hitCenter != oldHitCenter)
423 || (hitArc != oldHitArc)
424 || (hitRotate != oldHitRotate)
425 || (hitSpan != oldHitSpan))
432 /*virtual*/ void Arc::Enumerate(FILE * file)
434 fprintf(file, "ARC %i (%lf,%lf) %lf, %lf, %lf\n", layer, position.x, position.y, radius, startAngle, angleSpan);
438 /*virtual*/ Object * Arc::Copy(void)
440 #warning "!!! This doesn't take care of attached Dimensions !!!"
442 This is a real problem. While having a pointer in the Dimension to this line's points
443 is fast & easy, it creates a huge problem when trying to replicate an object like this.
445 Maybe a way to fix that then, is to have reference numbers instead of pointers. That
446 way, if you copy them, ... you might still have problems. Because you can't be sure if
447 a copy will be persistant or not, you then *definitely* do not want them to have the
448 same reference number.
450 return new Arc(position, radius, startAngle, angleSpan, parent);