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
20 #include "mathconstants.h"
24 Arc::Arc(Vector p1, double r, double a1, double a2, Object * p/*= NULL*/):
25 Object(p1, p), /*type(OTArc),*/ radius(r), startAngle(a1), angleSpan(a2),
26 draggingCenter(false), draggingEdge(false), draggingRotate(false),
28 hitCenter(false), hitArc(false), hitRotate(false), hitSpan(false)
30 // This is in the base class, why can't we use the contructor to fill it???
41 /*virtual*/ void Arc::Draw(Painter * painter)
44 painter->SetPen(QPen(Qt::red, 2.0, Qt::DotLine));
46 Point p1(cos(startAngle), sin(startAngle));
47 Point p2(cos(startAngle + angleSpan), sin(startAngle + angleSpan));
48 Vector handle2 = (p1 * radius) + position;
49 Vector handle3 = (p2 * radius) + position;
51 if ((state == OSSelected) || ((state == OSInactive) && hitRotate))
52 painter->DrawHandle(handle2);
54 if ((state == OSSelected) || ((state == OSInactive) && hitSpan))
55 painter->DrawHandle(handle3);
57 if ((state == OSSelected) || ((state == OSInactive) && hitCenter))
58 painter->DrawHandle(position);
60 if ((state == OSInactive) && !hitArc)
61 painter->SetPen(QPen(Qt::black, 1.0, Qt::SolidLine));
63 painter->DrawArc(position, radius, startAngle, angleSpan);
65 if (draggingRotate || draggingSpan)
69 // If we rotating, we draw a guideline showing the angle we're
71 Point p3(cos(oldAngle), sin(oldAngle));
72 Vector oldLine = (p3 * (radius * 1.25)) + position;
73 pen = QPen(QColor(0x80, 0x80, 0x80), 1.0, Qt::DashLine);
75 // painter->DrawLine((int)position.x, (int)position.y, (int)oldLine.x, (int)oldLine.y);
76 painter->DrawLine(position, oldLine);
79 // In rotating and setting the span, we draw a line showing where
80 // we angle/span is that we're setting.
81 pen = QPen(QColor(0x00, 0xC0, 0x80), 1.0, Qt::DashLine);
83 painter->DrawLine((int)position.x, (int)position.y, (int)oldPoint.x, (int)oldPoint.y);
86 // If we're rotating or setting the span, draw an information panel
87 // showing both absolute and relative angles being set.
88 if (draggingRotate || draggingSpan || draggingEdge)
90 double absAngle = (Vector(oldPoint - position).Angle()) * RADIANS_TO_DEGREES;
91 double relAngle = (startAngle >= oldAngle ? startAngle - oldAngle :
92 startAngle - oldAngle + (2.0 * PI)) * RADIANS_TO_DEGREES;
98 text = QObject::tr("Abs ") + QChar(0x2221) + ": %1" + QChar(0x00B0)
99 + QObject::tr("\nRel ") + QChar(0x2221) + ": %2" + QChar(0x00B0);
100 text = text.arg(absAngle, 0, 'd', 4).arg(relAngle, 0, 'd', 4);
102 else if (draggingSpan)
104 text = QObject::tr("Abs ") + QChar(0x2221) + ": %1" + QChar(0x00B0)
105 + QObject::tr("\nSpan: %2") + QChar(0x00B0);
106 text = text.arg(absAngle, 0, 'd', 4).arg(angleSpan * RADIANS_TO_DEGREES, 0, 'd', 4);
108 else if (draggingEdge)
110 text = QObject::tr("Radius: %1\nScale: %2%");
111 text = text.arg(radius, 0, 'd', 4).arg(radius / oldRadius * 100.0, 0, 'd', 0);
115 pen = QPen(QColor(0x00, 0xFF, 0x00), 1.0, Qt::SolidLine);
116 painter->SetPen(pen);
117 painter->SetBrush(QBrush(QColor(0x40, 0xFF, 0x40, 0x9F)));
118 QRectF textRect(10.0, 10.0, 270.0, 70.0); // x, y, w, h
119 painter->DrawRoundedRect(textRect, 7.0, 7.0);
121 textRect.setLeft(textRect.left() + 14);
122 painter->SetFont(*Object::font);
123 pen = QPen(QColor(0x00, 0x5F, 0xDF));
124 painter->SetPen(pen);
125 painter->DrawText(textRect, Qt::AlignVCenter, text);
127 painter->DrawInformativeText(text);
133 /*virtual*/ Vector Arc::Center(void)
140 We need at least *four* handles for this object:
144 - one for setting the span of the arc
146 We need to think about the intuitive way (if there is any) to grab and
147 manipulate a complex object like this... Need to think, "What should happen when
148 I click here and drag there?"
150 Also: should put the snap logic into the Object base class (as a static method)...
153 /*virtual*/ bool Arc::Collided(Vector point)
155 // Someone told us to fuck off, so we'll fuck off. :-)
159 objectWasDragged = false;
160 bool hitSomething = HitTest(point);
161 draggingCenter = hitCenter;
162 draggingEdge = hitArc;
163 draggingRotate = hitRotate;
164 draggingSpan = hitSpan;
166 // Now that we've done our hit testing on the non-snapped point, snap it if
169 point = SnapPointToGrid(point);
171 if (snapPointIsValid)
176 We want the arc to go into OSSelected mode if we click on it but don't drag.
177 If we don't click anywhere on the arc, then we want it to go into OSInactive mode.
178 Otherwise, we hit a handle and we want to modify the object whether it's in
179 OSSelected mode or not.
181 If I, go to S. Can drag, but only handles 2, 3, & 4 (since the center is not highlighted
183 However, if dragging, revert to I once finished.
185 If S, stay in S. Can drag all.
187 So, we know this. If wasDragged is true, then there's a chance we need to revert to I.
190 So. We have a matrix that looks like this:
204 so let's do like this:
212 oldAngle = startAngle;
222 /*virtual*/ bool Arc::PointerMoved(Vector point)
224 // one other thing to check here for is if a modifier key is being held as well,
225 // to allow for multi-selection
226 if (selectionInProgress)
228 // Check for whether or not the rect contains this circle
229 // if (selection.normalized().contains(Extents()))
230 if (selection.contains(Extents()))
238 // The TLC will send these messages if the object is selected but not clicked on.
239 // So we have to be careful with our assumptions here.
240 // This is actually untrue in that case, we need to come up with something better
242 // objectWasDragged = true;
243 // needUpdate = false;
245 bool hovered = HitTest(point);
246 needUpdate = HitStateChanged();
249 point = SnapPointToGrid(point);
251 if (snapPointIsValid)
254 objectWasDragged = (draggingCenter | draggingEdge | draggingRotate | draggingSpan);
256 if (objectWasDragged)
258 // if (!(hitHandle1 || hitHandle2 || hitHandle3 || hitHandle4))
261 // Vector delta = point - oldPoint;
265 else if (draggingEdge)
266 radius = Vector::Magnitude(point, position);
267 else if (draggingRotate)
269 startAngle = Vector(point - position).Angle();
271 else if (draggingSpan)
273 double angle = Vector(point - position).Angle();
275 if (angle < startAngle)
278 angleSpan = angle - startAngle;
281 // Why save this? For rendering code?
283 // needUpdate = true;
288 /*virtual*/ void Arc::PointerReleased(void)
290 // Mouse went up, so our dragging is done (if any *was* done, that is)
291 // hitHandle1 = hitHandle2 = hitHandle3 = hitHandle4 = false;
292 draggingCenter = draggingEdge = draggingRotate = draggingSpan = false;
293 hitCenter = hitArc = hitRotate = hitSpan = false;
295 // If the object was dragged, then revert to the old state.
296 // Otherwise, we were probably just clicked, and want to stay in the selected state.
297 if (objectWasDragged)
302 /*virtual*/ bool Arc::HitTest(Point point)
304 hitCenter = hitArc = hitRotate = hitSpan = false;
308 the center of the arc
311 The point on a unit circle given an angle a is x = cos(a), y = sin(a)
312 This vector is already unitized, so all we need to do to get our point is to
313 multiply it by radius (to get the length correct) and add it to the center
314 point (to get the correct position).
316 // Vector v1(point, position); // Head minus tail (vector points at "point")
317 Vector v1(position, point); // Head minus tail (vector points at "point")
318 Point p1(cos(startAngle), sin(startAngle));
319 Point p2(cos(startAngle + angleSpan), sin(startAngle + angleSpan));
320 Vector handle2 = (p1 * radius) + position;
321 Vector handle3 = (p2 * radius) + position;
322 double pointerAngle = v1.Angle();
323 double length = v1.Magnitude();
326 if (v1.Magnitude() < 10.0)
328 else if (Vector(handle3 - point).Magnitude() < 10.0)
330 else if (Vector(handle2 - point).Magnitude() < 10.0)
332 else if ((v1.Magnitude() < radius + 3.0) && (v1.Magnitude() > radius - 3.0)
333 && AngleInArcSpan(pointerAngle))
336 if ((length * Painter::zoom) < 8.0)
339 snapPoint = position;
340 snapPointIsValid = true;
342 else if (((fabs(length - radius) * Painter::zoom) < 2.0)
343 && AngleInArcSpan(pointerAngle))
345 else if ((Vector::Magnitude(handle2, point) * Painter::zoom) < 8.0)
349 snapPointIsValid = true;
351 else if ((Vector::Magnitude(handle3, point) * Painter::zoom) < 8.0)
355 snapPointIsValid = true;
359 return (hitCenter || hitArc || hitRotate || hitSpan ? true : false);
363 /*virtual*/ QRectF Arc::Extents(void)
365 double start = startAngle;
366 double end = start + angleSpan;
367 QPointF p1(cos(start), sin(start));
368 QPointF p2(cos(end), sin(end));
369 QRectF bounds(p1, p2);
372 // Swap X/Y coordinates if they're backwards...
373 if (bounds.left() > bounds.right())
375 double temp = bounds.left();
376 bounds.setLeft(bounds.right());
377 bounds.setRight(temp);
380 if (bounds.bottom() > bounds.top())
382 double temp = bounds.bottom();
383 bounds.setBottom(bounds.top());
387 bounds = bounds.normalized();
390 // If the end of the arc is before the beginning, add 360 degrees to it
394 // Adjust the bounds depending on which axes are crossed
395 if ((start < PI_OVER_2) && (end > PI_OVER_2))
398 if ((start < PI) && (end > PI))
399 bounds.setLeft(-1.0);
401 if ((start < (PI + PI_OVER_2)) && (end > (PI + PI_OVER_2)))
402 bounds.setBottom(-1.0);
404 if ((start < (2.0 * PI)) && (end > (2.0 * PI)))
405 bounds.setRight(1.0);
407 if ((start < ((2.0 * PI) + PI_OVER_2)) && (end > ((2.0 * PI) + PI_OVER_2)))
410 if ((start < (3.0 * PI)) && (end > (3.0 * PI)))
411 bounds.setLeft(-1.0);
413 if ((start < ((3.0 * PI) + PI_OVER_2)) && (end > ((3.0 * PI) + PI_OVER_2)))
414 bounds.setBottom(-1.0);
416 bounds.setTopLeft(QPointF(bounds.left() * radius, bounds.top() * radius));
417 bounds.setBottomRight(QPointF(bounds.right() * radius, bounds.bottom() * radius));
418 bounds.translate(position.x, position.y);
424 start = 350, span = 20, end = 10, angle = 5
425 angle < start, so angle = 365
427 bool Arc::AngleInArcSpan(double angle)
429 // This should be simple except for a small complication: The start angle plus
430 // the angle span can end up being less than the start angle! So, to check
431 // for this possibility, we check to see if the angle passed in is less than
432 // the start angle and if so, add 2PI to the angle passed in. Then we take the
433 // difference between our start angle and this adjusted angle and see if it
434 // falls within our span.
435 if (angle < startAngle)
438 double passedInSpan = angle - startAngle;
440 return (passedInSpan <= angleSpan ? true : false);
444 void Arc::SaveHitState(void)
446 oldHitCenter = hitCenter;
448 oldHitRotate = hitRotate;
449 oldHitSpan = hitSpan;
453 bool Arc::HitStateChanged(void)
455 if ((hitCenter != oldHitCenter)
456 || (hitArc != oldHitArc)
457 || (hitRotate != oldHitRotate)
458 || (hitSpan != oldHitSpan))
465 /*virtual*/ void Arc::Enumerate(FILE * file)
467 fprintf(file, "ARC %i (%lf,%lf) %lf, %lf, %lf\n", layer, position.x, position.y, radius, startAngle, angleSpan);
471 /*virtual*/ Object * Arc::Copy(void)
473 #warning "!!! This doesn't take care of attached Dimensions !!!"
475 This is a real problem. While having a pointer in the Dimension to this line's points
476 is fast & easy, it creates a huge problem when trying to replicate an object like this.
478 Maybe a way to fix that then, is to have reference numbers instead of pointers. That
479 way, if you copy them, ... you might still have problems. Because you can't be sure if
480 a copy will be persistant or not, you then *definitely* do not want them to have the
481 same reference number.
483 return new Arc(position, radius, startAngle, angleSpan, parent);
487 /*virtual*/ void Arc::Rotate(Point point, double angle)
489 Point c1 = Geometry::RotatePointAroundPoint(position, point, angle);
490 Point ap1(cos(startAngle), sin(startAngle));
491 Point angleStartPoint = (ap1 * radius) + position;
492 Point c2 = Geometry::RotatePointAroundPoint(angleStartPoint, point, angle);
495 startAngle = Vector(c1, c2).Angle();
499 /*virtual*/ void Arc::Mirror(Point p1, Point p2)
501 Point c1 = Geometry::MirrorPointAroundLine(position, p1, p2);
502 Point ap1(cos(startAngle + angleSpan), sin(startAngle + angleSpan));
503 Point angleEndPoint = (ap1 * radius) + position;
504 Point c2 = Geometry::MirrorPointAroundLine(angleEndPoint, p1, p2);
507 startAngle = Vector(c2, c1).Angle();
511 /*virtual*/ void Arc::Save(void)
515 oldStartAngle = startAngle;
516 oldAngleSpan = angleSpan;
520 /*virtual*/ void Arc::Restore(void)
524 startAngle = oldStartAngle;
525 angleSpan = oldAngleSpan;