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);
77 // In rotating and setting the span, we draw a line showing where
78 // we angle/span is that we're setting.
79 pen = QPen(QColor(0x00, 0xC0, 0x80), 1.0, Qt::DashLine);
81 painter->DrawLine((int)position.x, (int)position.y, (int)oldPoint.x, (int)oldPoint.y);
84 // If we're rotating or setting the span, draw an information panel
85 // showing both absolute and relative angles being set.
86 if (draggingRotate || draggingSpan || draggingEdge)
88 double absAngle = (Vector(oldPoint - position).Angle()) * RADIANS_TO_DEGREES;
89 double relAngle = (startAngle >= oldAngle ? startAngle - oldAngle :
90 startAngle - oldAngle + (2.0 * PI)) * RADIANS_TO_DEGREES;
96 text = QObject::tr("Abs ") + QChar(0x2221) + ": %1" + QChar(0x00B0)
97 + QObject::tr("\nRel ") + QChar(0x2221) + ": %2" + QChar(0x00B0);
98 text = text.arg(absAngle, 0, 'd', 4).arg(relAngle, 0, 'd', 4);
100 else if (draggingSpan)
102 text = QObject::tr("Abs ") + QChar(0x2221) + ": %1" + QChar(0x00B0)
103 + QObject::tr("\nSpan: %2") + QChar(0x00B0);
104 text = text.arg(absAngle, 0, 'd', 4).arg(angleSpan * RADIANS_TO_DEGREES, 0, 'd', 4);
106 else if (draggingEdge)
108 text = QObject::tr("Radius: %1\nScale: %2%");
109 text = text.arg(radius, 0, 'd', 4).arg(radius / oldRadius * 100.0, 0, 'd', 0);
112 pen = QPen(QColor(0x00, 0xFF, 0x00), 1.0, Qt::SolidLine);
113 painter->SetPen(pen);
114 painter->SetBrush(QBrush(QColor(0x40, 0xFF, 0x40, 0x9F)));
115 QRectF textRect(10.0, 10.0, 270.0, 70.0); // x, y, w, h
116 painter->DrawRoundedRect(textRect, 7.0, 7.0);
118 textRect.setLeft(textRect.left() + 14);
119 painter->SetFont(*Object::font);
120 pen = QPen(QColor(0x00, 0x5F, 0xDF));
121 painter->SetPen(pen);
122 painter->DrawText(textRect, Qt::AlignVCenter, text);
127 /*virtual*/ Vector Arc::Center(void)
134 We need at least *four* handles for this object:
138 - one for setting the span of the arc
140 We need to think about the intuitive way (if there is any) to grab and
141 manipulate a complex object like this... Need to think, "What should happen when
142 I click here and drag there?"
144 Also: should put the snap logic into the Object base class (as a static method)...
147 /*virtual*/ bool Arc::Collided(Vector point)
149 objectWasDragged = false;
150 // Vector v1 = point - position; // Head minus tail (vector points at "point")
153 bool hitSomething = HitTest(point);
154 draggingCenter = hitCenter;
155 draggingEdge = hitArc;
156 draggingRotate = hitRotate;
157 draggingSpan = hitSpan;
159 // Check for collision with various things...
160 hitHandle1 = false; // Moving
161 hitHandle2 = false; // Rotation
162 hitHandle3 = false; // Setting span of the arc
163 hitHandle4 = false; // Resizing
166 the center of the arc
169 The point on a unit circle given an angle a is x = cos(a), y = sin(a)
170 This vector is already unitized, so all we need to do to get our point is to multiply it by
171 radius (to get the length correct) and add it to the center point (to get the correct position).
173 Vector v1 = point - position; // Head minus tail (vector points at "point")
174 Point p1(cos(startAngle), sin(startAngle));
175 Point p2(cos(startAngle + angleSpan), sin(startAngle + angleSpan));
176 Vector handle2 = (p1 * radius) + position;
177 Vector handle3 = (p2 * radius) + position;
178 double pointerAngle = v1.Angle();
182 if (v1.Magnitude() < 10.0)
185 else if (Vector(handle3 - point).Magnitude() < 10.0)
188 else if (Vector(handle2 - point).Magnitude() < 10.0)
190 // Resize handle (the arc itself)
191 else if ((v1.Magnitude() < radius + 3.0) && (v1.Magnitude() > radius - 3.0)
192 && AngleInArcSpan(pointerAngle))
199 We want the arc to go into OSSelected mode if we click on it but don't drag.
200 If we don't click anywhere on the arc, then we want it to go into OSInactive mode.
201 Otherwise, we hit a handle and we want to modify the object whether it's in
202 OSSelected mode or not.
204 If I, go to S. Can drag, but only handles 2, 3, & 4 (since the center is not highlighted
206 However, if dragging, revert to I once finished.
208 If S, stay in S. Can drag all.
210 So, we know this. If wasDragged is true, then there's a chance we need to revert to I.
213 So. We have a matrix that looks like this:
227 so let's do like this:
229 // if (hitCenter || hitArc || hitRotate || hitSpan)
236 oldAngle = startAngle;
246 /*virtual*/ void Arc::PointerMoved(Vector point)
248 // one other thing to check here for is if a modifier key is being held as well,
249 // to allow for multi-selection
250 if (selectionInProgress)
252 // Check for whether or not the rect contains this circle
253 // if (selection.normalized().contains(Extents()))
254 if (selection.contains(Extents()))
262 // The TLC will send these messages if the object is selected but not clicked on.
263 // So we have to be careful with our assumptions here.
264 // This is actually untrue in that case, we need to come up with something better
266 // objectWasDragged = true;
267 // needUpdate = false;
270 needUpdate = HitStateChanged();
271 objectWasDragged = (draggingCenter | draggingEdge | draggingRotate | draggingSpan);
273 if (objectWasDragged)
275 // if (!(hitHandle1 || hitHandle2 || hitHandle3 || hitHandle4))
278 // Vector delta = point - oldPoint;
282 else if (draggingEdge)
283 radius = Vector::Magnitude(point, position);
284 else if (draggingRotate)
286 startAngle = Vector(point - position).Angle();
288 else if (draggingSpan)
290 double angle = Vector(point - position).Angle();
292 if (angle < startAngle)
295 angleSpan = angle - startAngle;
298 // Why save this? For rendering code?
300 // needUpdate = true;
304 /*virtual*/ void Arc::PointerReleased(void)
306 // Mouse went up, so our dragging is done (if any *was* done, that is)
307 // hitHandle1 = hitHandle2 = hitHandle3 = hitHandle4 = false;
308 draggingCenter = draggingEdge = draggingRotate = draggingSpan = false;
309 hitCenter = hitArc = hitRotate = hitSpan = false;
311 // If the object was dragged, then revert to the old state.
312 // Otherwise, we were probably just clicked, and want to stay in the selected state.
313 if (objectWasDragged)
318 /*virtual*/ bool Arc::HitTest(Point point)
320 hitCenter = hitArc = hitRotate = hitSpan = false;
324 the center of the arc
327 The point on a unit circle given an angle a is x = cos(a), y = sin(a)
328 This vector is already unitized, so all we need to do to get our point is to
329 multiply it by radius (to get the length correct) and add it to the center
330 point (to get the correct position).
332 Vector v1(point, position); // Head minus tail (vector points at "point")
333 Point p1(cos(startAngle), sin(startAngle));
334 Point p2(cos(startAngle + angleSpan), sin(startAngle + angleSpan));
335 Vector handle2 = (p1 * radius) + position;
336 Vector handle3 = (p2 * radius) + position;
337 double pointerAngle = v1.Angle();
338 double length = v1.Magnitude();
341 if (v1.Magnitude() < 10.0)
343 else if (Vector(handle3 - point).Magnitude() < 10.0)
345 else if (Vector(handle2 - point).Magnitude() < 10.0)
347 else if ((v1.Magnitude() < radius + 3.0) && (v1.Magnitude() > radius - 3.0)
348 && AngleInArcSpan(pointerAngle))
351 if ((length * Painter::zoom) < 8.0)
353 else if (((fabs(length - radius) * Painter::zoom) < 2.0)
354 && AngleInArcSpan(pointerAngle))
356 else if ((Vector::Magnitude(handle2, point) * Painter::zoom) < 8.0)
358 else if ((Vector::Magnitude(handle3, point) * Painter::zoom) < 8.0)
362 return (hitCenter || hitArc || hitRotate || hitSpan ? true : false);
366 /*virtual*/ QRectF Arc::Extents(void)
368 double start = startAngle;
369 double end = start + angleSpan;
370 QPointF p1(cos(start), sin(start));
371 QPointF p2(cos(end), sin(end));
372 QRectF bounds(p1, p2);
374 // Swap X/Y coordinates if they're backwards...
375 if (bounds.left() > bounds.right())
377 double temp = bounds.left();
378 bounds.setLeft(bounds.right());
379 bounds.setRight(temp);
382 if (bounds.bottom() > bounds.top())
384 double temp = bounds.bottom();
385 bounds.setBottom(bounds.top());
389 // If the end of the arc is before the beginning, add 360 degrees to it
393 // Adjust the bounds depending on which axes are crossed
394 if ((start < PI_OVER_2) && (end > PI_OVER_2))
397 if ((start < PI) && (end > PI))
398 bounds.setLeft(-1.0);
400 if ((start < (PI + PI_OVER_2)) && (end > (PI + PI_OVER_2)))
401 bounds.setBottom(-1.0);
403 if ((start < (2.0 * PI)) && (end > (2.0 * PI)))
404 bounds.setRight(1.0);
406 if ((start < ((2.0 * PI) + PI_OVER_2)) && (end > ((2.0 * PI) + PI_OVER_2)))
409 if ((start < (3.0 * PI)) && (end > (3.0 * PI)))
410 bounds.setLeft(-1.0);
412 if ((start < ((3.0 * PI) + PI_OVER_2)) && (end > ((3.0 * PI) + PI_OVER_2)))
413 bounds.setBottom(-1.0);
415 bounds.setTopLeft(QPointF(bounds.left() * radius, bounds.top() * radius));
416 bounds.setBottomRight(QPointF(bounds.right() * radius, bounds.bottom() * radius));
417 bounds.translate(position.x, position.y);
423 start = 350, span = 20, end = 10, angle = 5
424 angle < start, so angle = 365
426 bool Arc::AngleInArcSpan(double angle)
428 // This should be simple except for a small complication: The start angle plus
429 // the angle span can end up being less than the start angle! So, to check
430 // for this possibility, we check to see if the angle passed in is less than
431 // the start angle and if so, add 2PI to the angle passed in. Then we take the
432 // difference between our start angle and this adjusted angle and see if it
433 // falls within our span.
434 if (angle < startAngle)
437 double passedInSpan = angle - startAngle;
439 return (passedInSpan <= angleSpan ? true : false);
443 void Arc::SaveHitState(void)
445 oldHitCenter = hitCenter;
447 oldHitRotate = hitRotate;
448 oldHitSpan = hitSpan;
452 bool Arc::HitStateChanged(void)
454 if ((hitCenter != oldHitCenter)
455 || (hitArc != oldHitArc)
456 || (hitRotate != oldHitRotate)
457 || (hitSpan != oldHitSpan))
464 /*virtual*/ void Arc::Enumerate(FILE * file)
466 fprintf(file, "ARC (%lf,%lf) %lf, %lf, %lf\n", position.x, position.y, radius, startAngle, angleSpan);
470 /*virtual*/ Object * Arc::Copy(void)
472 #warning "!!! This doesn't take care of attached Dimensions !!!"
474 This is a real problem. While having a pointer in the Dimension to this line's points
475 is fast & easy, it creates a huge problem when trying to replicate an object like this.
477 Maybe a way to fix that then, is to have reference numbers instead of pointers. That
478 way, if you copy them, ... you might still have problems. Because you can't be sure if
479 a copy will be persistant or not, you then *definitely* do not want them to have the
480 same reference number.
482 return new Arc(position, radius, startAngle, angleSpan, parent);